home *** CD-ROM | disk | FTP | other *** search
/ Ultra Pack / UltraComputing Partner Applications.iso / SunLabs / tclTK / src / tk4.0 / tkMenu.c < prev    next >
C/C++ Source or Header  |  1995-06-25  |  83KB  |  2,651 lines

  1. /* 
  2.  * tkMenu.c --
  3.  *
  4.  *    This module implements menus for the Tk toolkit.  The menus
  5.  *    support normal button entries, plus check buttons, radio
  6.  *    buttons, iconic forms of all of the above, and separator
  7.  *    entries.
  8.  *
  9.  * Copyright (c) 1990-1994 The Regents of the University of California.
  10.  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
  11.  *
  12.  * See the file "license.terms" for information on usage and redistribution
  13.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  14.  */
  15.  
  16. static char sccsid[] = "@(#) tkMenu.c 1.92 95/06/25 16:11:06";
  17.  
  18. #include "tkPort.h"
  19. #include "default.h"
  20. #include "tkInt.h"
  21.  
  22. /*
  23.  * One of the following data structures is kept for each entry of each
  24.  * menu managed by this file:
  25.  */
  26.  
  27. typedef struct MenuEntry {
  28.     int type;            /* Type of menu entry;  see below for
  29.                  * valid types. */
  30.     struct Menu *menuPtr;    /* Menu with which this entry is associated. */
  31.     char *label;        /* Main text label displayed in entry (NULL
  32.                  * if no label).  Malloc'ed. */
  33.     int labelLength;        /* Number of non-NULL characters in label. */
  34.     int underline;        /* Index of character to underline. */
  35.     Pixmap bitmap;        /* Bitmap to display in menu entry, or None.
  36.                  * If not None then label is ignored. */
  37.     char *imageString;        /* Name of image to display (malloc'ed), or
  38.                  * NULL.  If non-NULL, bitmap, text, and
  39.                  * textVarName are ignored. */
  40.     Tk_Image image;        /* Image to display in menu entry, or NULL if
  41.                  * none. */
  42.     char *selectImageString;    /* Name of image to display when selected
  43.                  * (malloc'ed), or NULL. */
  44.     Tk_Image selectImage;    /* Image to display in entry when selected,
  45.                  * or NULL if none.  Ignored if image is
  46.                  * NULL. */
  47.     char *accel;        /* Accelerator string displayed at right
  48.                  * of menu entry.  NULL means no such
  49.                  * accelerator.  Malloc'ed. */
  50.     int accelLength;        /* Number of non-NULL characters in
  51.                  * accelerator. */
  52.  
  53.     /*
  54.      * Information related to displaying entry:
  55.      */
  56.  
  57.     Tk_Uid state;        /* State of button for display purposes:
  58.                  * normal, active, or disabled. */
  59.     int height;            /* Number of pixels occupied by entry in
  60.                  * vertical dimension. */
  61.     int y;            /* Y-coordinate of topmost pixel in entry. */
  62.     int indicatorOn;        /* True means draw indicator, false means
  63.                  * don't draw it. */
  64.     int indicatorDiameter;    /* Size of indicator display, in pixels. */
  65.     Tk_3DBorder border;        /* Structure used to draw background for
  66.                  * entry.  NULL means use overall border
  67.                  * for menu. */
  68.     XColor *fg;            /* Foreground color to use for entry.  NULL
  69.                  * means use foreground color from menu. */
  70.     Tk_3DBorder activeBorder;    /* Used to draw background and border when
  71.                  * element is active.  NULL means use
  72.                  * activeBorder from menu. */
  73.     XColor *activeFg;        /* Foreground color to use when entry is
  74.                  * active.  NULL means use active foreground
  75.                  * from menu. */
  76.     XFontStruct *fontPtr;    /* Text font for menu entries.  NULL means
  77.                  * use overall font for menu. */
  78.     GC textGC;            /* GC for drawing text in entry.  NULL means
  79.                  * use overall textGC for menu. */
  80.     GC activeGC;        /* GC for drawing text in entry when active.
  81.                  * NULL means use overall activeGC for
  82.                  * menu. */
  83.     GC disabledGC;        /* Used to produce disabled effect for entry.
  84.                  * NULL means use overall disabledGC from
  85.                  * menu structure.  See comments for
  86.                  * disabledFg in menu structure for more
  87.                  * information. */
  88.     XColor *indicatorFg;    /* Color for indicators in radio and check
  89.                  * button entries.  NULL means use indicatorFg
  90.                  * GC from menu. */
  91.     GC indicatorGC;        /* For drawing indicators.  None means use
  92.                  * GC from menu. */
  93.  
  94.     /*
  95.      * Information used to implement this entry's action:
  96.      */
  97.  
  98.     char *command;        /* Command to invoke when entry is invoked.
  99.                  * Malloc'ed. */
  100.     char *name;            /* Name of variable (for check buttons and
  101.                  * radio buttons) or menu (for cascade
  102.                  * entries).  Malloc'ed.*/
  103.     char *onValue;        /* Value to store in variable when selected
  104.                  * (only for radio and check buttons).
  105.                  * Malloc'ed. */
  106.     char *offValue;        /* Value to store in variable when not
  107.                  * selected (only for check buttons).
  108.                  * Malloc'ed. */
  109.  
  110.     /*
  111.      * Miscellaneous information:
  112.      */
  113.  
  114.     int flags;            /* Various flags.  See below for definitions. */
  115. } MenuEntry;
  116.  
  117. /*
  118.  * Flag values defined for menu entries:
  119.  *
  120.  * ENTRY_SELECTED:        Non-zero means this is a radio or check
  121.  *                button and that it should be drawn in
  122.  *                the "selected" state.
  123.  * ENTRY_NEEDS_REDISPLAY:    Non-zero means the entry should be redisplayed.
  124.  */
  125.  
  126. #define ENTRY_SELECTED        1
  127. #define ENTRY_NEEDS_REDISPLAY    4
  128.  
  129. /*
  130.  * Types defined for MenuEntries:
  131.  */
  132.  
  133. #define COMMAND_ENTRY        0
  134. #define SEPARATOR_ENTRY        1
  135. #define CHECK_BUTTON_ENTRY    2
  136. #define RADIO_BUTTON_ENTRY    3
  137. #define CASCADE_ENTRY        4
  138. #define TEAROFF_ENTRY        5
  139.  
  140. /*
  141.  * Mask bits for above types:
  142.  */
  143.  
  144. #define COMMAND_MASK        TK_CONFIG_USER_BIT
  145. #define SEPARATOR_MASK        (TK_CONFIG_USER_BIT << 1)
  146. #define CHECK_BUTTON_MASK    (TK_CONFIG_USER_BIT << 2)
  147. #define RADIO_BUTTON_MASK    (TK_CONFIG_USER_BIT << 3)
  148. #define CASCADE_MASK        (TK_CONFIG_USER_BIT << 4)
  149. #define TEAROFF_MASK        (TK_CONFIG_USER_BIT << 5)
  150. #define ALL_MASK        (COMMAND_MASK | SEPARATOR_MASK \
  151.     | CHECK_BUTTON_MASK | RADIO_BUTTON_MASK | CASCADE_MASK | TEAROFF_MASK)
  152.  
  153. /*
  154.  * Configuration specs for individual menu entries:
  155.  */
  156.  
  157. static Tk_ConfigSpec entryConfigSpecs[] = {
  158.     {TK_CONFIG_BORDER, "-activebackground", (char *) NULL, (char *) NULL,
  159.     DEF_MENU_ENTRY_ACTIVE_BG, Tk_Offset(MenuEntry, activeBorder),
  160.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  161.     |TK_CONFIG_NULL_OK},
  162.     {TK_CONFIG_COLOR, "-activeforeground", (char *) NULL, (char *) NULL,
  163.     DEF_MENU_ENTRY_ACTIVE_FG, Tk_Offset(MenuEntry, activeFg),
  164.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  165.     |TK_CONFIG_NULL_OK},
  166.     {TK_CONFIG_STRING, "-accelerator", (char *) NULL, (char *) NULL,
  167.     DEF_MENU_ENTRY_ACCELERATOR, Tk_Offset(MenuEntry, accel),
  168.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  169.     |TK_CONFIG_NULL_OK},
  170.     {TK_CONFIG_BORDER, "-background", (char *) NULL, (char *) NULL,
  171.     DEF_MENU_ENTRY_BG, Tk_Offset(MenuEntry, border),
  172.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  173.     |TK_CONFIG_NULL_OK},
  174.     {TK_CONFIG_BITMAP, "-bitmap", (char *) NULL, (char *) NULL,
  175.     DEF_MENU_ENTRY_BITMAP, Tk_Offset(MenuEntry, bitmap),
  176.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  177.     |TK_CONFIG_NULL_OK},
  178.     {TK_CONFIG_STRING, "-command", (char *) NULL, (char *) NULL,
  179.     DEF_MENU_ENTRY_COMMAND, Tk_Offset(MenuEntry, command),
  180.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  181.     |TK_CONFIG_NULL_OK},
  182.     {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
  183.     DEF_MENU_ENTRY_FONT, Tk_Offset(MenuEntry, fontPtr),
  184.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  185.     |TK_CONFIG_NULL_OK},
  186.     {TK_CONFIG_COLOR, "-foreground", (char *) NULL, (char *) NULL,
  187.     DEF_MENU_ENTRY_FG, Tk_Offset(MenuEntry, fg),
  188.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  189.     |TK_CONFIG_NULL_OK},
  190.     {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL,
  191.     DEF_MENU_ENTRY_IMAGE, Tk_Offset(MenuEntry, imageString),
  192.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  193.     |TK_CONFIG_NULL_OK},
  194.     {TK_CONFIG_BOOLEAN, "-indicatoron", (char *) NULL, (char *) NULL,
  195.     DEF_MENU_ENTRY_INDICATOR, Tk_Offset(MenuEntry, indicatorOn),
  196.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_DONT_SET_DEFAULT},
  197.     {TK_CONFIG_STRING, "-label", (char *) NULL, (char *) NULL,
  198.     DEF_MENU_ENTRY_LABEL, Tk_Offset(MenuEntry, label),
  199.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK},
  200.     {TK_CONFIG_STRING, "-menu", (char *) NULL, (char *) NULL,
  201.     DEF_MENU_ENTRY_MENU, Tk_Offset(MenuEntry, name),
  202.     CASCADE_MASK|TK_CONFIG_NULL_OK},
  203.     {TK_CONFIG_STRING, "-offvalue", (char *) NULL, (char *) NULL,
  204.     DEF_MENU_ENTRY_OFF_VALUE, Tk_Offset(MenuEntry, offValue),
  205.     CHECK_BUTTON_MASK},
  206.     {TK_CONFIG_STRING, "-onvalue", (char *) NULL, (char *) NULL,
  207.     DEF_MENU_ENTRY_ON_VALUE, Tk_Offset(MenuEntry, onValue),
  208.     CHECK_BUTTON_MASK},
  209.     {TK_CONFIG_COLOR, "-selectcolor", (char *) NULL, (char *) NULL,
  210.     DEF_MENU_ENTRY_SELECT, Tk_Offset(MenuEntry, indicatorFg),
  211.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  212.     {TK_CONFIG_STRING, "-selectimage", (char *) NULL, (char *) NULL,
  213.     DEF_MENU_ENTRY_SELECT_IMAGE, Tk_Offset(MenuEntry, selectImageString),
  214.     CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  215.     {TK_CONFIG_UID, "-state", (char *) NULL, (char *) NULL,
  216.     DEF_MENU_ENTRY_STATE, Tk_Offset(MenuEntry, state),
  217.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  218.     |TEAROFF_MASK|TK_CONFIG_DONT_SET_DEFAULT},
  219.     {TK_CONFIG_STRING, "-value", (char *) NULL, (char *) NULL,
  220.     DEF_MENU_ENTRY_VALUE, Tk_Offset(MenuEntry, onValue),
  221.     RADIO_BUTTON_MASK|TK_CONFIG_NULL_OK},
  222.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  223.     DEF_MENU_ENTRY_CHECK_VARIABLE, Tk_Offset(MenuEntry, name),
  224.     CHECK_BUTTON_MASK|TK_CONFIG_NULL_OK},
  225.     {TK_CONFIG_STRING, "-variable", (char *) NULL, (char *) NULL,
  226.     DEF_MENU_ENTRY_RADIO_VARIABLE, Tk_Offset(MenuEntry, name),
  227.     RADIO_BUTTON_MASK},
  228.     {TK_CONFIG_INT, "-underline", (char *) NULL, (char *) NULL,
  229.     DEF_MENU_ENTRY_UNDERLINE, Tk_Offset(MenuEntry, underline),
  230.     COMMAND_MASK|CHECK_BUTTON_MASK|RADIO_BUTTON_MASK|CASCADE_MASK
  231.     |TK_CONFIG_DONT_SET_DEFAULT},
  232.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  233.     (char *) NULL, 0, 0}
  234. };
  235.  
  236. /*
  237.  * A data structure of the following type is kept for each
  238.  * menu managed by this file:
  239.  */
  240.  
  241. typedef struct Menu {
  242.     Tk_Window tkwin;        /* Window that embodies the pane.  NULL
  243.                  * means that the window has been destroyed
  244.                  * but the data structures haven't yet been
  245.                  * cleaned up.*/
  246.     Display *display;        /* Display containing widget.  Needed, among
  247.                  * other things, so that resources can be
  248.                  * freed up even after tkwin has gone away. */
  249.     Tcl_Interp *interp;        /* Interpreter associated with menu. */
  250.     Tcl_Command widgetCmd;    /* Token for menu's widget command. */
  251.     MenuEntry **entries;    /* Array of pointers to all the entries
  252.                  * in the menu.  NULL means no entries. */
  253.     int numEntries;        /* Number of elements in entries. */
  254.     int active;            /* Index of active entry.  -1 means
  255.                  * nothing active. */
  256.  
  257.     /*
  258.      * Information used when displaying widget:
  259.      */
  260.  
  261.     Tk_3DBorder border;        /* Structure used to draw 3-D
  262.                  * border and background for menu. */
  263.     int borderWidth;        /* Width of border around whole menu. */
  264.     Tk_3DBorder activeBorder;    /* Used to draw background and border for
  265.                  * active element (if any). */
  266.     int activeBorderWidth;    /* Width of border around active element. */
  267.     int relief;            /* 3-d effect: TK_RELIEF_RAISED, etc. */
  268.     XFontStruct *fontPtr;    /* Text font for menu entries. */
  269.     XColor *fg;            /* Foreground color for entries. */
  270.     GC textGC;            /* GC for drawing text and other features
  271.                  * of menu entries. */
  272.     XColor *disabledFg;        /* Foreground color when disabled.  NULL
  273.                  * means use normalFg with a 50% stipple
  274.                  * instead. */
  275.     Pixmap gray;        /* Bitmap for drawing disabled entries in
  276.                  * a stippled fashion.  None means not
  277.                  * allocated yet. */
  278.     GC disabledGC;        /* Used to produce disabled effect.  If
  279.                  * disabledFg isn't NULL, this GC is used to
  280.                  * draw text and icons for disabled entries.
  281.                  * Otherwise text and icons are drawn with
  282.                  * normalGC and this GC is used to stipple
  283.                  * background across them. */
  284.     XColor *activeFg;        /* Foreground color for active entry. */
  285.     GC activeGC;        /* GC for drawing active entry. */
  286.     XColor *indicatorFg;    /* Color for indicators in radio and check
  287.                  * button entries. */
  288.     GC indicatorGC;        /* For drawing indicators. */
  289.     int indicatorSpace;        /* Number of pixels to allow for displaying
  290.                  * indicators in menu entries (includes extra
  291.                  * space around indicator). */
  292.     int labelWidth;        /* Number of pixels to allow for displaying
  293.                  * labels in menu entries. */
  294.  
  295.     /*
  296.      * Miscellaneous information:
  297.      */
  298.  
  299.     int tearOff;        /* 1 means this is a tear-off menu, so the
  300.                  * first entry always shows a dashed stripe
  301.                  * for tearing off. */
  302.     Cursor cursor;        /* Current cursor for window, or None. */
  303.     char *takeFocus;        /* Value of -takefocus option;  not used in
  304.                  * the C code, but used by keyboard traversal
  305.                  * scripts.  Malloc'ed, but may be NULL. */
  306.     char *postCommand;        /* Command to execute just before posting
  307.                  * this menu, or NULL.  Malloc-ed. */
  308.     MenuEntry *postedCascade;    /* Points to menu entry for cascaded
  309.                  * submenu that is currently posted, or
  310.                  * NULL if no submenu posted. */
  311.     int flags;            /* Various flags;  see below for
  312.                  * definitions. */
  313. } Menu;
  314.  
  315. /*
  316.  * Flag bits for menus:
  317.  *
  318.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  319.  *                has already been queued to redraw
  320.  *                this window.
  321.  * RESIZE_PENDING:        Non-zero means a call to ComputeMenuGeometry
  322.  *                has already been scheduled.
  323.  */
  324.  
  325. #define REDRAW_PENDING        1
  326. #define RESIZE_PENDING        2
  327.  
  328. /*
  329.  * Configuration specs valid for the menu as a whole:
  330.  */
  331.  
  332. static Tk_ConfigSpec configSpecs[] = {
  333.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  334.     DEF_MENU_ACTIVE_BG_COLOR, Tk_Offset(Menu, activeBorder),
  335.     TK_CONFIG_COLOR_ONLY},
  336.     {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
  337.     DEF_MENU_ACTIVE_BG_MONO, Tk_Offset(Menu, activeBorder),
  338.     TK_CONFIG_MONO_ONLY},
  339.     {TK_CONFIG_PIXELS, "-activeborderwidth", "activeBorderWidth", "BorderWidth",
  340.     DEF_MENU_ACTIVE_BORDER_WIDTH, Tk_Offset(Menu, activeBorderWidth), 0},
  341.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  342.     DEF_MENU_ACTIVE_FG_COLOR, Tk_Offset(Menu, activeFg),
  343.     TK_CONFIG_COLOR_ONLY},
  344.     {TK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
  345.     DEF_MENU_ACTIVE_FG_MONO, Tk_Offset(Menu, activeFg),
  346.     TK_CONFIG_MONO_ONLY},
  347.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  348.     DEF_MENU_BG_COLOR, Tk_Offset(Menu, border), TK_CONFIG_COLOR_ONLY},
  349.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  350.     DEF_MENU_BG_MONO, Tk_Offset(Menu, border), TK_CONFIG_MONO_ONLY},
  351.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  352.     (char *) NULL, 0, 0},
  353.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  354.     (char *) NULL, 0, 0},
  355.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  356.     DEF_MENU_BORDER_WIDTH, Tk_Offset(Menu, borderWidth), 0},
  357.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  358.     DEF_MENU_CURSOR, Tk_Offset(Menu, cursor), TK_CONFIG_NULL_OK},
  359.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  360.     "DisabledForeground", DEF_MENU_DISABLED_FG_COLOR,
  361.     Tk_Offset(Menu, disabledFg), TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK},
  362.     {TK_CONFIG_COLOR, "-disabledforeground", "disabledForeground",
  363.     "DisabledForeground", DEF_MENU_DISABLED_FG_MONO,
  364.     Tk_Offset(Menu, disabledFg), TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK},
  365.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  366.     (char *) NULL, 0, 0},
  367.     {TK_CONFIG_FONT, "-font", "font", "Font",
  368.     DEF_MENU_FONT, Tk_Offset(Menu, fontPtr), 0},
  369.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  370.     DEF_MENU_FG, Tk_Offset(Menu, fg), 0},
  371.     {TK_CONFIG_STRING, "-postcommand", "postCommand", "Command",
  372.     DEF_MENU_POST_COMMAND, Tk_Offset(Menu, postCommand), TK_CONFIG_NULL_OK},
  373.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  374.     DEF_MENU_RELIEF, Tk_Offset(Menu, relief), 0},
  375.     {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
  376.     DEF_MENU_SELECT_COLOR, Tk_Offset(Menu, indicatorFg),
  377.     TK_CONFIG_COLOR_ONLY},
  378.     {TK_CONFIG_COLOR, "-selectcolor", "selectColor", "Background",
  379.     DEF_MENU_SELECT_MONO, Tk_Offset(Menu, indicatorFg),
  380.     TK_CONFIG_MONO_ONLY},
  381.     {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
  382.     DEF_MENU_TAKE_FOCUS, Tk_Offset(Menu, takeFocus), TK_CONFIG_NULL_OK},
  383.     {TK_CONFIG_BOOLEAN, "-tearoff", "tearOff", "TearOff",
  384.     DEF_MENU_TEAROFF, Tk_Offset(Menu, tearOff), 0},
  385.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  386.     (char *) NULL, 0, 0}
  387. };
  388.  
  389. /*
  390.  * Various geometry definitions:
  391.  */
  392.  
  393. #define CASCADE_ARROW_HEIGHT 10
  394. #define CASCADE_ARROW_WIDTH 8
  395. #define DECORATION_BORDER_WIDTH 2
  396. #define MARGIN_WIDTH 2
  397.  
  398. /*
  399.  * Forward declarations for procedures defined later in this file:
  400.  */
  401.  
  402. static int        ActivateMenuEntry _ANSI_ARGS_((Menu *menuPtr,
  403.                 int index));
  404. static void        ComputeMenuGeometry _ANSI_ARGS_((
  405.                 ClientData clientData));
  406. static int        ConfigureMenu _ANSI_ARGS_((Tcl_Interp *interp,
  407.                 Menu *menuPtr, int argc, char **argv,
  408.                 int flags));
  409. static int        ConfigureMenuEntry _ANSI_ARGS_((Tcl_Interp *interp,
  410.                 Menu *menuPtr, MenuEntry *mePtr, int index,
  411.                 int argc, char **argv, int flags));
  412. static void        DestroyMenu _ANSI_ARGS_((ClientData clientData));
  413. static void        DestroyMenuEntry _ANSI_ARGS_((ClientData clientData));
  414. static void        DisplayMenu _ANSI_ARGS_((ClientData clientData));
  415. static void        EventuallyRedrawMenu _ANSI_ARGS_((Menu *menuPtr,
  416.                 MenuEntry *mePtr));
  417. static int        GetMenuIndex _ANSI_ARGS_((Tcl_Interp *interp,
  418.                 Menu *menuPtr, char *string, int lastOK,
  419.                 int *indexPtr));
  420. static int        MenuAddOrInsert _ANSI_ARGS_((Tcl_Interp *interp,
  421.                 Menu *menuPtr, char *indexString, int argc,
  422.                 char **argv));
  423. static void        MenuCmdDeletedProc _ANSI_ARGS_((
  424.                 ClientData clientData));
  425. static void        MenuEventProc _ANSI_ARGS_((ClientData clientData,
  426.                 XEvent *eventPtr));
  427. static void        MenuImageProc _ANSI_ARGS_((ClientData clientData,
  428.                 int x, int y, int width, int height, int imgWidth,
  429.                 int imgHeight));
  430. static MenuEntry *    MenuNewEntry _ANSI_ARGS_((Menu *menuPtr, int index,
  431.                 int type));
  432. static void        MenuSelectImageProc _ANSI_ARGS_((ClientData clientData,
  433.                 int x, int y, int width, int height, int imgWidth,
  434.                 int imgHeight));
  435. static char *        MenuVarProc _ANSI_ARGS_((ClientData clientData,
  436.                 Tcl_Interp *interp, char *name1, char *name2,
  437.                 int flags));
  438. static int        MenuWidgetCmd _ANSI_ARGS_((ClientData clientData,
  439.                 Tcl_Interp *interp, int argc, char **argv));
  440. static int        PostSubmenu _ANSI_ARGS_((Tcl_Interp *interp,
  441.                 Menu *menuPtr, MenuEntry *mePtr));
  442.  
  443. /*
  444.  *--------------------------------------------------------------
  445.  *
  446.  * Tk_MenuCmd --
  447.  *
  448.  *    This procedure is invoked to process the "menu" Tcl
  449.  *    command.  See the user documentation for details on
  450.  *    what it does.
  451.  *
  452.  * Results:
  453.  *    A standard Tcl result.
  454.  *
  455.  * Side effects:
  456.  *    See the user documentation.
  457.  *
  458.  *--------------------------------------------------------------
  459.  */
  460.  
  461. int
  462. Tk_MenuCmd(clientData, interp, argc, argv)
  463.     ClientData clientData;    /* Main window associated with
  464.                  * interpreter. */
  465.     Tcl_Interp *interp;        /* Current interpreter. */
  466.     int argc;            /* Number of arguments. */
  467.     char **argv;        /* Argument strings. */
  468. {
  469.     Tk_Window tkwin = (Tk_Window) clientData;
  470.     Tk_Window new;
  471.     register Menu *menuPtr;
  472.     XSetWindowAttributes atts;
  473.  
  474.     if (argc < 2) {
  475.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  476.         argv[0], " pathName ?options?\"", (char *) NULL);
  477.     return TCL_ERROR;
  478.     }
  479.  
  480.     /*
  481.      * Create the new window.  Set override-redirect so the window
  482.      * manager won't add a border or argue about placement, and set
  483.      * save-under so that the window can pop up and down without a
  484.      * lot of re-drawing.
  485.      */
  486.  
  487.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], "");
  488.     if (new == NULL) {
  489.     return TCL_ERROR;
  490.     }
  491.     atts.override_redirect = True;
  492.     atts.save_under = True;
  493.     Tk_ChangeWindowAttributes(new, CWOverrideRedirect|CWSaveUnder, &atts);
  494.  
  495.     /*
  496.      * Initialize the data structure for the menu.
  497.      */
  498.  
  499.     menuPtr = (Menu *) ckalloc(sizeof(Menu));
  500.     menuPtr->tkwin = new;
  501.     menuPtr->display = Tk_Display(new);
  502.     menuPtr->interp = interp;
  503.     menuPtr->widgetCmd = Tcl_CreateCommand(interp,
  504.         Tk_PathName(menuPtr->tkwin), MenuWidgetCmd,
  505.         (ClientData) menuPtr, MenuCmdDeletedProc);
  506.     menuPtr->entries = NULL;
  507.     menuPtr->numEntries = 0;
  508.     menuPtr->active = -1;
  509.     menuPtr->border = NULL;
  510.     menuPtr->borderWidth = 0;
  511.     menuPtr->relief = TK_RELIEF_FLAT;
  512.     menuPtr->activeBorder = NULL;
  513.     menuPtr->activeBorderWidth = 0;
  514.     menuPtr->fontPtr = NULL;
  515.     menuPtr->fg = NULL;
  516.     menuPtr->textGC = None;
  517.     menuPtr->disabledFg = NULL;
  518.     menuPtr->gray = None;
  519.     menuPtr->disabledGC = None;
  520.     menuPtr->activeFg = NULL;
  521.     menuPtr->activeGC = None;
  522.     menuPtr->indicatorFg = NULL;
  523.     menuPtr->indicatorGC = None;
  524.     menuPtr->indicatorSpace = 0;
  525.     menuPtr->labelWidth = 0;
  526.     menuPtr->tearOff = 1;
  527.     menuPtr->cursor = None;
  528.     menuPtr->takeFocus = NULL;
  529.     menuPtr->postCommand = NULL;
  530.     menuPtr->postedCascade = NULL;
  531.     menuPtr->flags = 0;
  532.  
  533.     Tk_SetClass(new, "Menu");
  534.     Tk_CreateEventHandler(menuPtr->tkwin, ExposureMask|StructureNotifyMask,
  535.         MenuEventProc, (ClientData) menuPtr);
  536.     if (ConfigureMenu(interp, menuPtr, argc-2, argv+2, 0) != TCL_OK) {
  537.     goto error;
  538.     }
  539.  
  540.     interp->result = Tk_PathName(menuPtr->tkwin);
  541.     return TCL_OK;
  542.  
  543.     error:
  544.     Tk_DestroyWindow(menuPtr->tkwin);
  545.     return TCL_ERROR;
  546. }
  547.  
  548. /*
  549.  *--------------------------------------------------------------
  550.  *
  551.  * MenuWidgetCmd --
  552.  *
  553.  *    This procedure is invoked to process the Tcl command
  554.  *    that corresponds to a widget managed by this module.
  555.  *    See the user documentation for details on what it does.
  556.  *
  557.  * Results:
  558.  *    A standard Tcl result.
  559.  *
  560.  * Side effects:
  561.  *    See the user documentation.
  562.  *
  563.  *--------------------------------------------------------------
  564.  */
  565.  
  566. static int
  567. MenuWidgetCmd(clientData, interp, argc, argv)
  568.     ClientData clientData;    /* Information about menu widget. */
  569.     Tcl_Interp *interp;        /* Current interpreter. */
  570.     int argc;            /* Number of arguments. */
  571.     char **argv;        /* Argument strings. */
  572. {
  573.     register Menu *menuPtr = (Menu *) clientData;
  574.     register MenuEntry *mePtr;
  575.     int result = TCL_OK;
  576.     size_t length;
  577.     int c;
  578.  
  579.     if (argc < 2) {
  580.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  581.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  582.     return TCL_ERROR;
  583.     }
  584.     Tk_Preserve((ClientData) menuPtr);
  585.     c = argv[1][0];
  586.     length = strlen(argv[1]);
  587.     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)
  588.         && (length >= 2)) {
  589.     int index;
  590.  
  591.     if (argc != 3) {
  592.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  593.             argv[0], " activate index\"", (char *) NULL);
  594.         goto error;
  595.     }
  596.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  597.         goto error;
  598.     }
  599.     if (menuPtr->active == index) {
  600.         goto done;
  601.     }
  602.     if (index >= 0) {
  603.         if ((menuPtr->entries[index]->type == SEPARATOR_ENTRY)
  604.             || (menuPtr->entries[index]->state == tkDisabledUid)) {
  605.         index = -1;
  606.         }
  607.     }
  608.     result = ActivateMenuEntry(menuPtr, index);
  609.     } else if ((c == 'a') && (strncmp(argv[1], "add", length) == 0)
  610.         && (length >= 2)) {
  611.     if (argc < 3) {
  612.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  613.             argv[0], " add type ?options?\"", (char *) NULL);
  614.         goto error;
  615.     }
  616.     if (MenuAddOrInsert(interp, menuPtr, (char *) NULL,
  617.         argc-2, argv+2) != TCL_OK) {
  618.         goto error;
  619.     }
  620.     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
  621.         && (length >= 2)) {
  622.     if (argc != 3) {
  623.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  624.             argv[0], " cget option\"",
  625.             (char *) NULL);
  626.         goto error;
  627.     }
  628.     result = Tk_ConfigureValue(interp, menuPtr->tkwin, configSpecs,
  629.         (char *) menuPtr, argv[2], 0);
  630.     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
  631.         && (length >= 2)) {
  632.     if (argc == 2) {
  633.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  634.             (char *) menuPtr, (char *) NULL, 0);
  635.     } else if (argc == 3) {
  636.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, configSpecs,
  637.             (char *) menuPtr, argv[2], 0);
  638.     } else {
  639.         result = ConfigureMenu(interp, menuPtr, argc-2, argv+2,
  640.             TK_CONFIG_ARGV_ONLY);
  641.     }
  642.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
  643.     int first, last, i, numDeleted;
  644.  
  645.     if ((argc != 3) && (argc != 4)) {
  646.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  647.             argv[0], " delete first ?last?\"", (char *) NULL);
  648.         goto error;
  649.     }
  650.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &first) != TCL_OK) {
  651.         goto error;
  652.     }
  653.     if (argc == 3) {
  654.         last = first;
  655.     } else {
  656.         if (GetMenuIndex(interp, menuPtr, argv[3], 0, &last) != TCL_OK) {
  657.             goto error;
  658.         }
  659.     }
  660.     if (menuPtr->tearOff && (first == 0)) {
  661.         /*
  662.          * Sorry, can't delete the tearoff entry;  must reconfigure
  663.          * the menu.
  664.          */
  665.         first = 1;
  666.     }
  667.     if ((first < 0) || (last < first)) {
  668.         goto done;
  669.     }
  670.     numDeleted = last + 1 - first;
  671.     for (i = first; i <= last; i++) {
  672.         Tk_EventuallyFree((ClientData) menuPtr->entries[i],
  673.                   DestroyMenuEntry);
  674.     }
  675.     for (i = last+1; i < menuPtr->numEntries; i++) {
  676.         menuPtr->entries[i-numDeleted] = menuPtr->entries[i];
  677.     }
  678.     menuPtr->numEntries -= numDeleted;
  679.     if ((menuPtr->active >= first) && (menuPtr->active <= last)) {
  680.         menuPtr->active = -1;
  681.     } else if (menuPtr->active > last) {
  682.         menuPtr->active -= numDeleted;
  683.     }
  684.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  685.         menuPtr->flags |= RESIZE_PENDING;
  686.         Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  687.     }
  688.     } else if ((c == 'e') && (length >= 7)
  689.         && (strncmp(argv[1], "entrycget", length) == 0)) {
  690.     int index;
  691.  
  692.     if (argc != 4) {
  693.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  694.             argv[0], " entrycget index option\"",
  695.             (char *) NULL);
  696.         goto error;
  697.     }
  698.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  699.         goto error;
  700.     }
  701.     if (index < 0) {
  702.         goto done;
  703.     }
  704.     mePtr = menuPtr->entries[index];
  705.     Tk_Preserve((ClientData) mePtr);
  706.     result = Tk_ConfigureValue(interp, menuPtr->tkwin, entryConfigSpecs,
  707.         (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  708.     Tk_Release((ClientData) mePtr);
  709.     } else if ((c == 'e') && (length >= 7)
  710.         && (strncmp(argv[1], "entryconfigure", length) == 0)) {
  711.     int index;
  712.  
  713.     if (argc < 3) {
  714.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  715.             argv[0], " entryconfigure index ?option value ...?\"",
  716.             (char *) NULL);
  717.         goto error;
  718.     }
  719.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  720.         goto error;
  721.     }
  722.     if (index < 0) {
  723.         goto done;
  724.     }
  725.     mePtr = menuPtr->entries[index];
  726.     Tk_Preserve((ClientData) mePtr);
  727.     if (argc == 3) {
  728.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  729.             (char *) mePtr, (char *) NULL,
  730.             COMMAND_MASK << mePtr->type);
  731.     } else if (argc == 4) {
  732.         result = Tk_ConfigureInfo(interp, menuPtr->tkwin, entryConfigSpecs,
  733.             (char *) mePtr, argv[3], COMMAND_MASK << mePtr->type);
  734.     } else {
  735.         result = ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc-3,
  736.             argv+3, TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  737.     }
  738.     Tk_Release((ClientData) mePtr);
  739.     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
  740.         && (length >= 3)) {
  741.     int index;
  742.  
  743.     if (argc != 3) {
  744.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  745.             argv[0], " index string\"", (char *) NULL);
  746.         goto error;
  747.     }
  748.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  749.         goto error;
  750.     }
  751.     if (index < 0) {
  752.         interp->result = "none";
  753.     } else {
  754.         sprintf(interp->result, "%d", index);
  755.     }
  756.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
  757.         && (length >= 3)) {
  758.     if (argc < 4) {
  759.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  760.             argv[0], " insert index type ?options?\"", (char *) NULL);
  761.         goto error;
  762.     }
  763.     if (MenuAddOrInsert(interp, menuPtr, argv[2],
  764.         argc-3, argv+3) != TCL_OK) {
  765.         goto error;
  766.     }
  767.     } else if ((c == 'i') && (strncmp(argv[1], "invoke", length) == 0)
  768.         && (length >= 3)) {
  769.     int index;
  770.  
  771.     if (argc != 3) {
  772.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  773.             argv[0], " invoke index\"", (char *) NULL);
  774.         goto error;
  775.     }
  776.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  777.         goto error;
  778.     }
  779.     if (index < 0) {
  780.         goto done;
  781.     }
  782.     mePtr = menuPtr->entries[index];
  783.     if (mePtr->state == tkDisabledUid) {
  784.         goto done;
  785.     }
  786.     Tk_Preserve((ClientData) mePtr);
  787.     if (mePtr->type == CHECK_BUTTON_ENTRY) {
  788.         if (mePtr->flags & ENTRY_SELECTED) {
  789.         if (Tcl_SetVar(interp, mePtr->name, mePtr->offValue,
  790.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  791.             result = TCL_ERROR;
  792.         }
  793.         } else {
  794.         if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  795.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  796.             result = TCL_ERROR;
  797.         }
  798.         }
  799.     } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  800.         if (Tcl_SetVar(interp, mePtr->name, mePtr->onValue,
  801.             TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) {
  802.         result = TCL_ERROR;
  803.         }
  804.     }
  805.     if ((result == TCL_OK) && (mePtr->command != NULL)) {
  806.         result = TkCopyAndGlobalEval(interp, mePtr->command);
  807.     }
  808.     if ((result == TCL_OK) && (mePtr->type == CASCADE_ENTRY)) {
  809.         result = PostSubmenu(menuPtr->interp, menuPtr, mePtr);
  810.     }
  811.     Tk_Release((ClientData) mePtr);
  812.     } else if ((c == 'p') && (strncmp(argv[1], "post", length) == 0)
  813.         && (length == 4)) {
  814.     int x, y, tmp, vRootX, vRootY;
  815.     int vRootWidth, vRootHeight;
  816.  
  817.     if (argc != 4) {
  818.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  819.             argv[0], " post x y\"", (char *) NULL);
  820.         goto error;
  821.     }
  822.     if ((Tcl_GetInt(interp, argv[2], &x) != TCL_OK)
  823.         || (Tcl_GetInt(interp, argv[3], &y) != TCL_OK)) {
  824.         goto error;
  825.     }
  826.  
  827.     /*
  828.      * De-activate any active element.
  829.      */
  830.  
  831.     ActivateMenuEntry(menuPtr, -1);
  832.  
  833.     /*
  834.      * If there is a command for the menu, execute it.  This
  835.      * may change the size of the menu, so be sure to recompute
  836.      * the menu's geometry if needed.
  837.      */
  838.  
  839.     if (menuPtr->postCommand != NULL) {
  840.         result = TkCopyAndGlobalEval(menuPtr->interp,
  841.             menuPtr->postCommand);
  842.         if (result != TCL_OK) {
  843.         return result;
  844.         }
  845.         if (menuPtr->flags & RESIZE_PENDING) {
  846.         Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  847.         ComputeMenuGeometry((ClientData) menuPtr);
  848.         }
  849.     }
  850.  
  851.     /*
  852.      * Adjust the position of the menu if necessary to keep it
  853.      * visible on the screen.  There are two special tricks to
  854.      * make this work right:
  855.      *
  856.      * 1. If a virtual root window manager is being used then
  857.      *    the coordinates are in the virtual root window of
  858.      *    menuPtr's parent;  since the menu uses override-redirect
  859.      *    mode it will be in the *real* root window for the screen,
  860.      *    so we have to map the coordinates from the virtual root
  861.      *    (if any) to the real root.  Can't get the virtual root
  862.      *    from the menu itself (it will never be seen by the wm)
  863.      *    so use its parent instead (it would be better to have an
  864.      *    an option that names a window to use for this...).
  865.      * 2. The menu may not have been mapped yet, so its current size
  866.      *    might be the default 1x1.  To compute how much space it
  867.      *    needs, use its requested size, not its actual size.
  868.      */
  869.  
  870.     Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY,
  871.         &vRootWidth, &vRootHeight);
  872.     x += vRootX;
  873.     y += vRootY;
  874.     tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin))
  875.         - Tk_ReqWidth(menuPtr->tkwin);
  876.     if (x > tmp) {
  877.         x = tmp;
  878.     }
  879.     if (x < 0) {
  880.         x = 0;
  881.     }
  882.     tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin))
  883.         - Tk_ReqHeight(menuPtr->tkwin);
  884.     if (y > tmp) {
  885.         y = tmp;
  886.     }
  887.     if (y < 0) {
  888.         y = 0;
  889.     }
  890.     if ((x != Tk_X(menuPtr->tkwin)) || (y != Tk_Y(menuPtr->tkwin))) {
  891.         Tk_MoveToplevelWindow(menuPtr->tkwin, x, y);
  892.     }
  893.     if (!Tk_IsMapped(menuPtr->tkwin)) {
  894.         Tk_MapWindow(menuPtr->tkwin);
  895.     }
  896.     XRaiseWindow(menuPtr->display, Tk_WindowId(menuPtr->tkwin));
  897.     } else if ((c == 'p') && (strncmp(argv[1], "postcascade", length) == 0)
  898.         && (length > 4)) {
  899.     int index;
  900.     if (argc != 3) {
  901.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  902.             argv[0], " postcascade index\"", (char *) NULL);
  903.         goto error;
  904.     }
  905.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  906.         goto error;
  907.     }
  908.     if ((index < 0) || (menuPtr->entries[index]->type != CASCADE_ENTRY)) {
  909.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  910.     } else {
  911.         result = PostSubmenu(interp, menuPtr, menuPtr->entries[index]);
  912.     }
  913.     } else if ((c == 't') && (strncmp(argv[1], "type", length) == 0)) {
  914.     int index;
  915.     if (argc != 3) {
  916.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  917.             argv[0], " type index\"", (char *) NULL);
  918.         goto error;
  919.     }
  920.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  921.         goto error;
  922.     }
  923.     if (index < 0) {
  924.         goto done;
  925.     }
  926.     mePtr = menuPtr->entries[index];
  927.     switch (mePtr->type) {
  928.         case COMMAND_ENTRY:
  929.         interp->result = "command";
  930.         break;
  931.         case SEPARATOR_ENTRY:
  932.         interp->result = "separator";
  933.         break;
  934.         case CHECK_BUTTON_ENTRY:
  935.         interp->result = "checkbutton";
  936.         break;
  937.         case RADIO_BUTTON_ENTRY:
  938.         interp->result = "radiobutton";
  939.         break;
  940.         case CASCADE_ENTRY:
  941.         interp->result = "cascade";
  942.         break;
  943.         case TEAROFF_ENTRY:
  944.         interp->result = "tearoff";
  945.         break;
  946.     }
  947.     } else if ((c == 'u') && (strncmp(argv[1], "unpost", length) == 0)) {
  948.     if (argc != 2) {
  949.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  950.             argv[0], " unpost\"", (char *) NULL);
  951.         goto error;
  952.     }
  953.     Tk_UnmapWindow(menuPtr->tkwin);
  954.     if (result == TCL_OK) {
  955.         result = PostSubmenu(interp, menuPtr, (MenuEntry *) NULL);
  956.     }
  957.     } else if ((c == 'y') && (strncmp(argv[1], "yposition", length) == 0)) {
  958.     int index;
  959.  
  960.     if (argc != 3) {
  961.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  962.             argv[0], " yposition index\"", (char *) NULL);
  963.         goto error;
  964.     }
  965.     if (GetMenuIndex(interp, menuPtr, argv[2], 0, &index) != TCL_OK) {
  966.         goto error;
  967.     }
  968.     if (index < 0) {
  969.         interp->result = "0";
  970.     } else {
  971.         sprintf(interp->result, "%d", menuPtr->entries[index]->y);
  972.     }
  973.     } else {
  974.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  975.         "\": must be activate, add, cget, configure, delete, ",
  976.         "entrycget, entryconfigure, index, insert, invoke, ",
  977.         "post, postcascade, type, unpost, or yposition",
  978.         (char *) NULL);
  979.     goto error;
  980.     }
  981.     done:
  982.     Tk_Release((ClientData) menuPtr);
  983.     return result;
  984.  
  985.     error:
  986.     Tk_Release((ClientData) menuPtr);
  987.     return TCL_ERROR;
  988. }
  989.  
  990. /*
  991.  *----------------------------------------------------------------------
  992.  *
  993.  * DestroyMenu --
  994.  *
  995.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  996.  *    to clean up the internal structure of a menu at a safe time
  997.  *    (when no-one is using it anymore).
  998.  *
  999.  * Results:
  1000.  *    None.
  1001.  *
  1002.  * Side effects:
  1003.  *    Everything associated with the menu is freed up.
  1004.  *
  1005.  *----------------------------------------------------------------------
  1006.  */
  1007.  
  1008. static void
  1009. DestroyMenu(clientData)
  1010.     ClientData clientData;    /* Info about menu widget. */
  1011. {
  1012.     register Menu *menuPtr = (Menu *) clientData;
  1013.     int i;
  1014.  
  1015.     /*
  1016.      * Free up all the stuff that requires special handling, then
  1017.      * let Tk_FreeOptions handle all the standard option-related
  1018.      * stuff.
  1019.      */
  1020.  
  1021.     for (i = 0; i < menuPtr->numEntries; i++) {
  1022.     DestroyMenuEntry((ClientData) menuPtr->entries[i]);
  1023.     }
  1024.     if (menuPtr->entries != NULL) {
  1025.     ckfree((char *) menuPtr->entries);
  1026.     }
  1027.     if (menuPtr->textGC != None) {
  1028.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  1029.     }
  1030.     if (menuPtr->gray != None) {
  1031.     Tk_FreeBitmap(menuPtr->display, menuPtr->gray);
  1032.     }
  1033.     if (menuPtr->disabledGC != None) {
  1034.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  1035.     }
  1036.     if (menuPtr->activeGC != None) {
  1037.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  1038.     }
  1039.     if (menuPtr->indicatorGC != None) {
  1040.     Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
  1041.     }
  1042.     Tk_FreeOptions(configSpecs, (char *) menuPtr, menuPtr->display, 0);
  1043.     ckfree((char *) menuPtr);
  1044. }
  1045.  
  1046. /*
  1047.  *----------------------------------------------------------------------
  1048.  *
  1049.  * DestroyMenuEntry --
  1050.  *
  1051.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  1052.  *    to clean up the internal structure of a menu entry at a safe time
  1053.  *    (when no-one is using it anymore).
  1054.  *
  1055.  * Results:
  1056.  *    None.
  1057.  *
  1058.  * Side effects:
  1059.  *    Everything associated with the menu entry is freed up.
  1060.  *
  1061.  *----------------------------------------------------------------------
  1062.  */
  1063.  
  1064. static void
  1065. DestroyMenuEntry(clientData)
  1066.     ClientData clientData;        /* Pointer to entry to be freed. */
  1067. {
  1068.     register MenuEntry *mePtr = (MenuEntry *) clientData;
  1069.     Menu *menuPtr = mePtr->menuPtr;
  1070.  
  1071.     /*
  1072.      * Free up all the stuff that requires special handling, then
  1073.      * let Tk_FreeOptions handle all the standard option-related
  1074.      * stuff.
  1075.      */
  1076.  
  1077.     if (menuPtr->postedCascade == mePtr) {
  1078.     /*
  1079.      * Ignore errors while unposting the menu, since it's possible
  1080.      * that the menu has already been deleted and the unpost will
  1081.      * generate an error.
  1082.      */
  1083.  
  1084.     PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL);
  1085.     }
  1086.     if (mePtr->image != NULL) {
  1087.     Tk_FreeImage(mePtr->image);
  1088.     }
  1089.     if (mePtr->textGC != None) {
  1090.     Tk_FreeGC(menuPtr->display, mePtr->textGC);
  1091.     }
  1092.     if (mePtr->activeGC != None) {
  1093.     Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  1094.     }
  1095.     if (mePtr->disabledGC != None) {
  1096.     Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  1097.     }
  1098.     if (mePtr->indicatorGC != None) {
  1099.     Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
  1100.     }
  1101.     if (mePtr->name != NULL) {
  1102.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1103.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1104.         MenuVarProc, (ClientData) mePtr);
  1105.     }
  1106.     Tk_FreeOptions(entryConfigSpecs, (char *) mePtr, menuPtr->display, 
  1107.         (COMMAND_MASK << mePtr->type));
  1108.     ckfree((char *) mePtr);
  1109. }
  1110.  
  1111. /*
  1112.  *----------------------------------------------------------------------
  1113.  *
  1114.  * ConfigureMenu --
  1115.  *
  1116.  *    This procedure is called to process an argv/argc list, plus
  1117.  *    the Tk option database, in order to configure (or
  1118.  *    reconfigure) a menu widget.
  1119.  *
  1120.  * Results:
  1121.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1122.  *    returned, then interp->result contains an error message.
  1123.  *
  1124.  * Side effects:
  1125.  *    Configuration information, such as colors, font, etc. get set
  1126.  *    for menuPtr;  old resources get freed, if there were any.
  1127.  *
  1128.  *----------------------------------------------------------------------
  1129.  */
  1130.  
  1131. static int
  1132. ConfigureMenu(interp, menuPtr, argc, argv, flags)
  1133.     Tcl_Interp *interp;        /* Used for error reporting. */
  1134.     register Menu *menuPtr;    /* Information about widget;  may or may
  1135.                  * not already have values for some fields. */
  1136.     int argc;            /* Number of valid entries in argv. */
  1137.     char **argv;        /* Arguments. */
  1138.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  1139. {
  1140.     XGCValues gcValues;
  1141.     GC newGC;
  1142.     unsigned long mask;
  1143.     int i;
  1144.  
  1145.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, configSpecs,
  1146.         argc, argv, (char *) menuPtr, flags) != TCL_OK) {
  1147.     return TCL_ERROR;
  1148.     }
  1149.  
  1150.     /*
  1151.      * A few options need special processing, such as setting the
  1152.      * background from a 3-D border, or filling in complicated
  1153.      * defaults that couldn't be specified to Tk_ConfigureWidget.
  1154.      */
  1155.  
  1156.     Tk_SetBackgroundFromBorder(menuPtr->tkwin, menuPtr->border);
  1157.  
  1158.     gcValues.font = menuPtr->fontPtr->fid;
  1159.     gcValues.foreground = menuPtr->fg->pixel;
  1160.     gcValues.background = Tk_3DBorderColor(menuPtr->border)->pixel;
  1161.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1162.         &gcValues);
  1163.     if (menuPtr->textGC != None) {
  1164.     Tk_FreeGC(menuPtr->display, menuPtr->textGC);
  1165.     }
  1166.     menuPtr->textGC = newGC;
  1167.  
  1168.     if (menuPtr->disabledFg != NULL) {
  1169.     gcValues.foreground = menuPtr->disabledFg->pixel;
  1170.     mask = GCForeground|GCBackground|GCFont;
  1171.     } else {
  1172.     gcValues.foreground = gcValues.background;
  1173.     if (menuPtr->gray == None) {
  1174.         menuPtr->gray = Tk_GetBitmap(interp, menuPtr->tkwin,
  1175.             Tk_GetUid("gray50"));
  1176.         if (menuPtr->gray == None) {
  1177.         return TCL_ERROR;
  1178.         }
  1179.     }
  1180.     gcValues.fill_style = FillStippled;
  1181.     gcValues.stipple = menuPtr->gray;
  1182.     mask = GCForeground|GCFillStyle|GCStipple;
  1183.     }
  1184.     newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1185.     if (menuPtr->disabledGC != None) {
  1186.     Tk_FreeGC(menuPtr->display, menuPtr->disabledGC);
  1187.     }
  1188.     menuPtr->disabledGC = newGC;
  1189.  
  1190.     gcValues.font = menuPtr->fontPtr->fid;
  1191.     gcValues.foreground = menuPtr->activeFg->pixel;
  1192.     gcValues.background = Tk_3DBorderColor(menuPtr->activeBorder)->pixel;
  1193.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont,
  1194.         &gcValues);
  1195.     if (menuPtr->activeGC != None) {
  1196.     Tk_FreeGC(menuPtr->display, menuPtr->activeGC);
  1197.     }
  1198.     menuPtr->activeGC = newGC;
  1199.  
  1200.     gcValues.foreground = menuPtr->indicatorFg->pixel;
  1201.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCFont, &gcValues);
  1202.     if (menuPtr->indicatorGC != None) {
  1203.     Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC);
  1204.     }
  1205.     menuPtr->indicatorGC = newGC;
  1206.  
  1207.     /*
  1208.      * After reconfiguring a menu, we need to reconfigure all of the
  1209.      * entries in the menu, since some of the things in the children
  1210.      * (such as graphics contexts) may have to change to reflect changes
  1211.      * in the parent.
  1212.      */
  1213.  
  1214.     for (i = 0; i < menuPtr->numEntries; i++) {
  1215.     MenuEntry *mePtr;
  1216.  
  1217.     mePtr = menuPtr->entries[i];
  1218.     ConfigureMenuEntry(interp, menuPtr, mePtr, i, 0, (char **) NULL,
  1219.         TK_CONFIG_ARGV_ONLY | COMMAND_MASK << mePtr->type);
  1220.     }
  1221.  
  1222.     /*
  1223.      * Depending on the -tearOff option, make sure that there is or
  1224.      * isn't an initial tear-off entry at the beginning of the menu.
  1225.      */
  1226.  
  1227.     if (menuPtr->tearOff) {
  1228.     if ((menuPtr->numEntries == 0)
  1229.         || (menuPtr->entries[0]->type != TEAROFF_ENTRY)) {
  1230.         MenuNewEntry(menuPtr, 0, TEAROFF_ENTRY);
  1231.     }
  1232.     } else if ((menuPtr->numEntries > 0)
  1233.         && (menuPtr->entries[0]->type == TEAROFF_ENTRY)) {
  1234.     Tk_EventuallyFree((ClientData) menuPtr->entries[0],
  1235.               DestroyMenuEntry);
  1236.     for (i = 1; i < menuPtr->numEntries;  i++) {
  1237.         menuPtr->entries[i-1] = menuPtr->entries[i];
  1238.     }
  1239.     menuPtr->numEntries--;
  1240.     }
  1241.  
  1242.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1243.     menuPtr->flags |= RESIZE_PENDING;
  1244.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1245.     }
  1246.  
  1247.     return TCL_OK;
  1248. }
  1249.  
  1250. /*
  1251.  *----------------------------------------------------------------------
  1252.  *
  1253.  * ConfigureMenuEntry --
  1254.  *
  1255.  *    This procedure is called to process an argv/argc list, plus
  1256.  *    the Tk option database, in order to configure (or
  1257.  *    reconfigure) one entry in a menu.
  1258.  *
  1259.  * Results:
  1260.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  1261.  *    returned, then interp->result contains an error message.
  1262.  *
  1263.  * Side effects:
  1264.  *    Configuration information such as label and accelerator get
  1265.  *    set for mePtr;  old resources get freed, if there were any.
  1266.  *
  1267.  *----------------------------------------------------------------------
  1268.  */
  1269.  
  1270. static int
  1271. ConfigureMenuEntry(interp, menuPtr, mePtr, index, argc, argv, flags)
  1272.     Tcl_Interp *interp;            /* Used for error reporting. */
  1273.     Menu *menuPtr;            /* Information about whole menu. */
  1274.     register MenuEntry *mePtr;        /* Information about menu entry;  may
  1275.                      * or may not already have values for
  1276.                      * some fields. */
  1277.     int index;                /* Index of mePtr within menuPtr's
  1278.                      * entries. */
  1279.     int argc;                /* Number of valid entries in argv. */
  1280.     char **argv;            /* Arguments. */
  1281.     int flags;                /* Additional flags to pass to
  1282.                      * Tk_ConfigureWidget. */
  1283. {
  1284.     XGCValues gcValues;
  1285.     GC newGC, newActiveGC, newDisabledGC;
  1286.     unsigned long mask;
  1287.     Tk_Image image;
  1288.  
  1289.     /*
  1290.      * If this entry is a cascade and the cascade is posted, then unpost
  1291.      * it before reconfiguring the entry (otherwise the reconfigure might
  1292.      * change the name of the cascaded entry, leaving a posted menu
  1293.      * high and dry).
  1294.      */
  1295.  
  1296.     if (menuPtr->postedCascade == mePtr) {
  1297.     if (PostSubmenu(menuPtr->interp, menuPtr, (MenuEntry *) NULL)
  1298.         != TCL_OK) {
  1299.         Tk_BackgroundError(menuPtr->interp);
  1300.     }
  1301.     }
  1302.  
  1303.     /*
  1304.      * If this entry is a check button or radio button, then remove
  1305.      * its old trace procedure.
  1306.      */
  1307.  
  1308.     if ((mePtr->name != NULL) &&
  1309.         ((mePtr->type == CHECK_BUTTON_ENTRY)
  1310.         || (mePtr->type == RADIO_BUTTON_ENTRY))) {
  1311.     Tcl_UntraceVar(menuPtr->interp, mePtr->name,
  1312.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1313.         MenuVarProc, (ClientData) mePtr);
  1314.     }
  1315.  
  1316.     if (Tk_ConfigureWidget(interp, menuPtr->tkwin, entryConfigSpecs,
  1317.         argc, argv, (char *) mePtr,
  1318.         flags | (COMMAND_MASK << mePtr->type)) != TCL_OK) {
  1319.     return TCL_ERROR;
  1320.     }
  1321.  
  1322.     /*
  1323.      * The code below handles special configuration stuff not taken
  1324.      * care of by Tk_ConfigureWidget, such as special processing for
  1325.      * defaults, sizing strings, graphics contexts, etc.
  1326.      */
  1327.  
  1328.     if (mePtr->label == NULL) {
  1329.     mePtr->labelLength = 0;
  1330.     } else {
  1331.     mePtr->labelLength = strlen(mePtr->label);
  1332.     }
  1333.     if (mePtr->accel == NULL) {
  1334.     mePtr->accelLength = 0;
  1335.     } else {
  1336.     mePtr->accelLength = strlen(mePtr->accel);
  1337.     }
  1338.  
  1339.     if (mePtr->state == tkActiveUid) {
  1340.     if (index != menuPtr->active) {
  1341.         ActivateMenuEntry(menuPtr, index);
  1342.     }
  1343.     } else {
  1344.     if (index == menuPtr->active) {
  1345.         ActivateMenuEntry(menuPtr, -1);
  1346.     }
  1347.     if ((mePtr->state != tkNormalUid) && (mePtr->state != tkDisabledUid)) {
  1348.         Tcl_AppendResult(interp, "bad state value \"", mePtr->state,
  1349.             "\":  must be normal, active, or disabled", (char *) NULL);
  1350.         mePtr->state = tkNormalUid;
  1351.         return TCL_ERROR;
  1352.     }
  1353.     }
  1354.  
  1355.     if ((mePtr->fontPtr != NULL) || (mePtr->border != NULL)
  1356.         || (mePtr->fg != NULL) || (mePtr->activeBorder != NULL)
  1357.         || (mePtr->activeFg != NULL)) {
  1358.     gcValues.foreground = (mePtr->fg != NULL) ? mePtr->fg->pixel
  1359.         : menuPtr->fg->pixel;
  1360.     gcValues.background = Tk_3DBorderColor(
  1361.         (mePtr->border != NULL) ? mePtr->border : menuPtr->border)
  1362.         ->pixel;
  1363.     gcValues.font = (mePtr->fontPtr != NULL) ? mePtr->fontPtr->fid
  1364.         : menuPtr->fontPtr->fid;
  1365.  
  1366.     /*
  1367.      * Note: disable GraphicsExpose events;  we know there won't be
  1368.      * obscured areas when copying from an off-screen pixmap to the
  1369.      * screen and this gets rid of unnecessary events.
  1370.      */
  1371.  
  1372.     gcValues.graphics_exposures = False;
  1373.     newGC = Tk_GetGC(menuPtr->tkwin,
  1374.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1375.         &gcValues);
  1376.  
  1377.     if (menuPtr->disabledFg != NULL) {
  1378.         gcValues.foreground = menuPtr->disabledFg->pixel;
  1379.         mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures;
  1380.     } else {
  1381.         gcValues.foreground = gcValues.background;
  1382.         gcValues.fill_style = FillStippled;
  1383.         gcValues.stipple = menuPtr->gray;
  1384.         mask = GCForeground|GCFillStyle|GCStipple;
  1385.     }
  1386.     newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues);
  1387.  
  1388.     gcValues.foreground = (mePtr->activeFg != NULL)
  1389.         ? mePtr->activeFg->pixel : menuPtr->activeFg->pixel;
  1390.     gcValues.background = Tk_3DBorderColor(
  1391.         (mePtr->activeBorder != NULL) ? mePtr->activeBorder
  1392.         : menuPtr->activeBorder)->pixel;
  1393.     newActiveGC = Tk_GetGC(menuPtr->tkwin,
  1394.         GCForeground|GCBackground|GCFont|GCGraphicsExposures,
  1395.         &gcValues);
  1396.     } else {
  1397.     newGC = None;
  1398.     newActiveGC = None;
  1399.     newDisabledGC = None;
  1400.     }
  1401.     if (mePtr->textGC != None) {
  1402.         Tk_FreeGC(menuPtr->display, mePtr->textGC);
  1403.     }
  1404.     mePtr->textGC = newGC;
  1405.     if (mePtr->activeGC != None) {
  1406.         Tk_FreeGC(menuPtr->display, mePtr->activeGC);
  1407.     }
  1408.     mePtr->activeGC = newActiveGC;
  1409.     if (mePtr->disabledGC != None) {
  1410.         Tk_FreeGC(menuPtr->display, mePtr->disabledGC);
  1411.     }
  1412.     mePtr->disabledGC = newDisabledGC;
  1413.     if (mePtr->indicatorFg != NULL) {
  1414.     gcValues.foreground = mePtr->indicatorFg->pixel;
  1415.     newGC = Tk_GetGC(menuPtr->tkwin, GCForeground, &gcValues);
  1416.     } else {
  1417.     newGC = None;
  1418.     }
  1419.     if (mePtr->indicatorGC != None) {
  1420.     Tk_FreeGC(menuPtr->display, mePtr->indicatorGC);
  1421.     }
  1422.     mePtr->indicatorGC = newGC;
  1423.  
  1424.     if ((mePtr->type == CHECK_BUTTON_ENTRY)
  1425.         || (mePtr->type == RADIO_BUTTON_ENTRY)) {
  1426.     char *value;
  1427.  
  1428.     if (mePtr->name == NULL) {
  1429.         mePtr->name = (char *) ckalloc((unsigned) (mePtr->labelLength + 1));
  1430.         strcpy(mePtr->name, (mePtr->label == NULL) ? "" : mePtr->label);
  1431.     }
  1432.     if (mePtr->onValue == NULL) {
  1433.         mePtr->onValue = (char *) ckalloc((unsigned)
  1434.             (mePtr->labelLength + 1));
  1435.         strcpy(mePtr->onValue, (mePtr->label == NULL) ? "" : mePtr->label);
  1436.     }
  1437.  
  1438.     /*
  1439.      * Select the entry if the associated variable has the
  1440.      * appropriate value, initialize the variable if it doesn't
  1441.      * exist, then set a trace on the variable to monitor future
  1442.      * changes to its value.
  1443.      */
  1444.  
  1445.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  1446.     mePtr->flags &= ~ENTRY_SELECTED;
  1447.     if (value != NULL) {
  1448.         if (strcmp(value, mePtr->onValue) == 0) {
  1449.         mePtr->flags |= ENTRY_SELECTED;
  1450.         }
  1451.     } else {
  1452.         Tcl_SetVar(interp, mePtr->name,
  1453.             (mePtr->type == CHECK_BUTTON_ENTRY) ? mePtr->offValue : "",
  1454.             TCL_GLOBAL_ONLY);
  1455.     }
  1456.     Tcl_TraceVar(interp, mePtr->name,
  1457.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  1458.         MenuVarProc, (ClientData) mePtr);
  1459.     }
  1460.  
  1461.     /*
  1462.      * Get the images for the entry, if there are any.  Allocate the
  1463.      * new images before freeing the old ones, so that the reference
  1464.      * counts don't go to zero and cause image data to be discarded.
  1465.      */
  1466.  
  1467.     if (mePtr->imageString != NULL) {
  1468.     image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->imageString,
  1469.         MenuImageProc, (ClientData) mePtr);
  1470.     if (image == NULL) {
  1471.         return TCL_ERROR;
  1472.     }
  1473.     } else {
  1474.     image = NULL;
  1475.     }
  1476.     if (mePtr->image != NULL) {
  1477.     Tk_FreeImage(mePtr->image);
  1478.     }
  1479.     mePtr->image = image;
  1480.     if (mePtr->selectImageString != NULL) {
  1481.     image = Tk_GetImage(interp, menuPtr->tkwin, mePtr->selectImageString,
  1482.         MenuSelectImageProc, (ClientData) mePtr);
  1483.     if (image == NULL) {
  1484.         return TCL_ERROR;
  1485.     }
  1486.     } else {
  1487.     image = NULL;
  1488.     }
  1489.     if (mePtr->selectImage != NULL) {
  1490.     Tk_FreeImage(mePtr->selectImage);
  1491.     }
  1492.     mePtr->selectImage = image;
  1493.  
  1494.     if (!(menuPtr->flags & RESIZE_PENDING)) {
  1495.     menuPtr->flags |= RESIZE_PENDING;
  1496.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  1497.     }
  1498.     return TCL_OK;
  1499. }
  1500.  
  1501. /*
  1502.  *--------------------------------------------------------------
  1503.  *
  1504.  * ComputeMenuGeometry --
  1505.  *
  1506.  *    This procedure is invoked to recompute the size and
  1507.  *    layout of a menu.  It is called as a when-idle handler so
  1508.  *    that it only gets done once, even if a group of changes is
  1509.  *    made to the menu.
  1510.  *
  1511.  * Results:
  1512.  *    None.
  1513.  *
  1514.  * Side effects:
  1515.  *    Fields of menu entries are changed to reflect their
  1516.  *    current positions, and the size of the menu window
  1517.  *    itself may be changed.
  1518.  *
  1519.  *--------------------------------------------------------------
  1520.  */
  1521.  
  1522. static void
  1523. ComputeMenuGeometry(clientData)
  1524.     ClientData clientData;        /* Structure describing menu. */
  1525. {
  1526.     Menu *menuPtr = (Menu *) clientData;
  1527.     register MenuEntry *mePtr;
  1528.     XFontStruct *fontPtr;
  1529.     int maxLabelWidth, maxIndicatorWidth, maxAccelWidth;
  1530.     int width, height, indicatorSpace;
  1531.     int i, y;
  1532.     int imageWidth, imageHeight;
  1533.  
  1534.     if (menuPtr->tkwin == NULL) {
  1535.     return;
  1536.     }
  1537.  
  1538.     maxLabelWidth = maxIndicatorWidth = maxAccelWidth = 0;
  1539.     y = menuPtr->borderWidth;
  1540.  
  1541.     for (i = 0; i < menuPtr->numEntries; i++) {
  1542.     mePtr = menuPtr->entries[i];
  1543.     indicatorSpace = 0;
  1544.     fontPtr = mePtr->fontPtr;
  1545.     if (fontPtr == NULL) {
  1546.         fontPtr = menuPtr->fontPtr;
  1547.     }
  1548.  
  1549.     /*
  1550.      * For each entry, compute the height required by that
  1551.      * particular entry, plus three widths:  the width of the
  1552.      * label, the width to allow for an indicator to be displayed
  1553.      * to the left of the label (if any), and the width of the
  1554.      * accelerator to be displayed to the right of the label
  1555.      * (if any).  These sizes depend, of course, on the type
  1556.      * of the entry.
  1557.      */
  1558.  
  1559.     if (mePtr->image != NULL) {
  1560.         Tk_SizeOfImage(mePtr->image, &imageWidth, &imageHeight);
  1561.  
  1562.         imageOrBitmap:
  1563.         mePtr->height = imageHeight;
  1564.         width = imageWidth;
  1565.         if (mePtr->indicatorOn) {
  1566.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1567.             indicatorSpace = (14*mePtr->height)/10;
  1568.             mePtr->indicatorDiameter = (65*mePtr->height)/100;
  1569.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1570.             indicatorSpace = (14*mePtr->height)/10;
  1571.             mePtr->indicatorDiameter = (75*mePtr->height)/100;
  1572.         }
  1573.         }
  1574.     } else if (mePtr->bitmap != None) {
  1575.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap,
  1576.             &imageWidth, &imageHeight);
  1577.         goto imageOrBitmap;
  1578.     } else {
  1579.         mePtr->height = fontPtr->ascent + fontPtr->descent;
  1580.         if (mePtr->label != NULL) {
  1581.         (void) TkMeasureChars(fontPtr, mePtr->label,
  1582.             mePtr->labelLength, 0, (int) 100000, 0,
  1583.             TK_NEWLINES_NOT_SPECIAL, &width);
  1584.         } else {
  1585.         width = 0;
  1586.         }
  1587.         if (mePtr->indicatorOn) {
  1588.         if (mePtr->type == CHECK_BUTTON_ENTRY) {
  1589.             indicatorSpace = mePtr->height;
  1590.             mePtr->indicatorDiameter = (80*mePtr->height)/100;
  1591.         } else if (mePtr->type == RADIO_BUTTON_ENTRY) {
  1592.             indicatorSpace = mePtr->height;
  1593.             mePtr->indicatorDiameter = mePtr->height;
  1594.         }
  1595.         }
  1596.     }
  1597.     mePtr->height += 2*menuPtr->activeBorderWidth + 2;
  1598.     if (width > maxLabelWidth) {
  1599.         maxLabelWidth = width;
  1600.     }
  1601.     if (mePtr->type == CASCADE_ENTRY) {
  1602.         width = 2*CASCADE_ARROW_WIDTH;
  1603.     } else if (mePtr->accel != NULL) {
  1604.         (void) TkMeasureChars(fontPtr, mePtr->accel, mePtr->accelLength,
  1605.             0, (int) 100000, 0, TK_NEWLINES_NOT_SPECIAL, &width);
  1606.     } else {
  1607.         width = 0;
  1608.     }
  1609.     if (width > maxAccelWidth) {
  1610.         maxAccelWidth = width;
  1611.     }
  1612.     if (mePtr->type == SEPARATOR_ENTRY) {
  1613.         mePtr->height = 8;
  1614.     }
  1615.     if (mePtr->type == TEAROFF_ENTRY) {
  1616.         mePtr->height = 12;
  1617.     }
  1618.     if (indicatorSpace > maxIndicatorWidth) {
  1619.         maxIndicatorWidth = indicatorSpace;
  1620.     }
  1621.     mePtr->y = y;
  1622.     y += mePtr->height;
  1623.     }
  1624.  
  1625.     /*
  1626.      * Got all the sizes.  Update fields in the menu structure, then
  1627.      * resize the window if necessary.  Leave margins on either side
  1628.      * of the indicator (or just one margin if there is no indicator).
  1629.      * Leave another margin on the right side of the label, plus yet
  1630.      * another margin to the right of the accelerator (if there is one).
  1631.      */
  1632.  
  1633.     menuPtr->indicatorSpace = maxIndicatorWidth + MARGIN_WIDTH;
  1634.     if (maxIndicatorWidth != 0) {
  1635.     menuPtr->indicatorSpace += MARGIN_WIDTH;
  1636.     }
  1637.     menuPtr->labelWidth = maxLabelWidth + MARGIN_WIDTH;
  1638.     width = menuPtr->indicatorSpace + menuPtr->labelWidth + maxAccelWidth
  1639.         + 2*menuPtr->borderWidth + 2*menuPtr->activeBorderWidth;
  1640.     if (maxAccelWidth != 0) {
  1641.     width += MARGIN_WIDTH;
  1642.     }
  1643.     height = y + menuPtr->borderWidth;
  1644.  
  1645.     /*
  1646.      * The X server doesn't like zero dimensions, so round up to at least
  1647.      * 1 (a zero-sized menu should never really occur, anyway).
  1648.      */
  1649.  
  1650.     if (width <= 0) {
  1651.     width = 1;
  1652.     }
  1653.     if (height <= 0) {
  1654.     height = 1;
  1655.     }
  1656.     if ((width != Tk_ReqWidth(menuPtr->tkwin)) ||
  1657.         (height != Tk_ReqHeight(menuPtr->tkwin))) {
  1658.     Tk_GeometryRequest(menuPtr->tkwin, width, height);
  1659.     } else {
  1660.     /*
  1661.      * Must always force a redisplay here if the window is mapped
  1662.      * (even if the size didn't change, something else might have
  1663.      * changed in the menu, such as a label or accelerator).  The
  1664.      * resize will force a redisplay above.
  1665.      */
  1666.  
  1667.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  1668.     }
  1669.  
  1670.     menuPtr->flags &= ~RESIZE_PENDING;
  1671. }
  1672.  
  1673. /*
  1674.  *----------------------------------------------------------------------
  1675.  *
  1676.  * DisplayMenu --
  1677.  *
  1678.  *    This procedure is invoked to display a menu widget.
  1679.  *
  1680.  * Results:
  1681.  *    None.
  1682.  *
  1683.  * Side effects:
  1684.  *    Commands are output to X to display the menu in its
  1685.  *    current mode.
  1686.  *
  1687.  *----------------------------------------------------------------------
  1688.  */
  1689.  
  1690. static void
  1691. DisplayMenu(clientData)
  1692.     ClientData clientData;    /* Information about widget. */
  1693. {
  1694.     register Menu *menuPtr = (Menu *) clientData;
  1695.     register MenuEntry *mePtr;
  1696.     register Tk_Window tkwin = menuPtr->tkwin;
  1697.     Tk_3DBorder bgBorder, activeBorder;
  1698.     XFontStruct *fontPtr;
  1699.     int index, baseline, strictMotif, leftEdge;
  1700.     GC gc;
  1701.     XPoint points[3];
  1702.  
  1703.     menuPtr->flags &= ~REDRAW_PENDING;
  1704.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  1705.     return;
  1706.     }
  1707.  
  1708.     /*
  1709.      * Loop through all of the entries, drawing them one at a time.
  1710.      */
  1711.  
  1712.     strictMotif = Tk_StrictMotif(menuPtr->tkwin);
  1713.     leftEdge = menuPtr->borderWidth + menuPtr->indicatorSpace
  1714.         + menuPtr->activeBorderWidth;
  1715.     for (index = 0; index < menuPtr->numEntries; index++) {
  1716.     mePtr = menuPtr->entries[index];
  1717.     if (!(mePtr->flags & ENTRY_NEEDS_REDISPLAY)) {
  1718.         continue;
  1719.     }
  1720.     mePtr->flags &= ~ENTRY_NEEDS_REDISPLAY;
  1721.  
  1722.     /*
  1723.      * Background.
  1724.      */
  1725.  
  1726.     bgBorder = mePtr->border;
  1727.     if (bgBorder == NULL) {
  1728.         bgBorder = menuPtr->border;
  1729.     }
  1730.     if (strictMotif) {
  1731.         activeBorder = bgBorder;
  1732.     } else {
  1733.         activeBorder = mePtr->activeBorder;
  1734.         if (activeBorder == NULL) {
  1735.         activeBorder = menuPtr->activeBorder;
  1736.         }
  1737.     }
  1738.     if (mePtr->state == tkActiveUid) {
  1739.         bgBorder = activeBorder;
  1740.         Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1741.             bgBorder, menuPtr->borderWidth, mePtr->y,
  1742.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1743.             menuPtr->activeBorderWidth, TK_RELIEF_RAISED);
  1744.         gc = mePtr->activeGC;
  1745.         if (gc == NULL) {
  1746.         gc = menuPtr->activeGC;
  1747.         }
  1748.     } else {
  1749.         Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1750.             bgBorder, menuPtr->borderWidth, mePtr->y,
  1751.             Tk_Width(tkwin) - 2*menuPtr->borderWidth, mePtr->height,
  1752.             0, TK_RELIEF_FLAT);
  1753.         if ((mePtr->state == tkDisabledUid)
  1754.             && (menuPtr->disabledFg != NULL)) {
  1755.         gc = mePtr->disabledGC;
  1756.         if (gc == NULL) {
  1757.             gc = menuPtr->disabledGC;
  1758.         }
  1759.         } else {
  1760.         gc = mePtr->textGC;
  1761.         if (gc == NULL) {
  1762.             gc = menuPtr->textGC;
  1763.         }
  1764.         }
  1765.     }
  1766.  
  1767.     /*
  1768.      * Draw label or bitmap or image for entry.
  1769.      */
  1770.  
  1771.     fontPtr = mePtr->fontPtr;
  1772.     if (fontPtr == NULL) {
  1773.         fontPtr = menuPtr->fontPtr;
  1774.     }
  1775.     baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1776.         - fontPtr->descent)/2;
  1777.     if (mePtr->image != NULL) {
  1778.         int width, height;
  1779.  
  1780.         Tk_SizeOfImage(mePtr->image, &width, &height);
  1781.         if ((mePtr->selectImage != NULL)
  1782.             && (mePtr->flags & ENTRY_SELECTED)) {
  1783.         Tk_RedrawImage(mePtr->selectImage, 0, 0, width, height,
  1784.             Tk_WindowId(tkwin), leftEdge,
  1785.             (int) (mePtr->y + (mePtr->height - height)/2));
  1786.         } else {
  1787.         Tk_RedrawImage(mePtr->image, 0, 0, width, height,
  1788.             Tk_WindowId(tkwin), leftEdge,
  1789.             (int) (mePtr->y + (mePtr->height - height)/2));
  1790.         }
  1791.     } else if (mePtr->bitmap != None) {
  1792.         int width, height;
  1793.  
  1794.         Tk_SizeOfBitmap(menuPtr->display, mePtr->bitmap, &width, &height);
  1795.         XCopyPlane(menuPtr->display, mePtr->bitmap, Tk_WindowId(tkwin),
  1796.             gc, 0, 0, (unsigned) width, (unsigned) height, leftEdge,
  1797.             (int) (mePtr->y + (mePtr->height - height)/2), 1);
  1798.     } else {
  1799.         baseline = mePtr->y + (mePtr->height + fontPtr->ascent
  1800.             - fontPtr->descent)/2;
  1801.         if (mePtr->label != NULL) {
  1802.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1803.             fontPtr, mePtr->label, mePtr->labelLength,
  1804.             leftEdge, baseline, leftEdge,
  1805.             TK_NEWLINES_NOT_SPECIAL);
  1806.         if (mePtr->underline >= 0) {
  1807.             TkUnderlineChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1808.                 fontPtr, mePtr->label, leftEdge, baseline,
  1809.                 leftEdge, TK_NEWLINES_NOT_SPECIAL,
  1810.                 mePtr->underline, mePtr->underline);
  1811.         }
  1812.         }
  1813.     }
  1814.  
  1815.     /*
  1816.      * Draw accelerator or cascade arrow.
  1817.      */
  1818.  
  1819.     if (mePtr->type == CASCADE_ENTRY) {
  1820.         points[0].x = Tk_Width(tkwin) - menuPtr->borderWidth
  1821.             - menuPtr->activeBorderWidth - MARGIN_WIDTH
  1822.             - CASCADE_ARROW_WIDTH;
  1823.         points[0].y = mePtr->y + (mePtr->height - CASCADE_ARROW_HEIGHT)/2;
  1824.         points[1].x = points[0].x;
  1825.         points[1].y = points[0].y + CASCADE_ARROW_HEIGHT;
  1826.         points[2].x = points[0].x + CASCADE_ARROW_WIDTH;
  1827.         points[2].y = points[0].y + CASCADE_ARROW_HEIGHT/2;
  1828.         Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin), activeBorder,
  1829.             points, 3, DECORATION_BORDER_WIDTH,
  1830.             (menuPtr->postedCascade == mePtr) ? TK_RELIEF_SUNKEN
  1831.             : TK_RELIEF_RAISED);
  1832.     } else if (mePtr->accel != NULL) {
  1833.         TkDisplayChars(menuPtr->display, Tk_WindowId(tkwin), gc,
  1834.             fontPtr, mePtr->accel, mePtr->accelLength,
  1835.             leftEdge + menuPtr->labelWidth, baseline,
  1836.             leftEdge + menuPtr->labelWidth, TK_NEWLINES_NOT_SPECIAL);
  1837.     }
  1838.  
  1839.     /*
  1840.      * Draw check-button indicator.
  1841.      */
  1842.  
  1843.     gc = mePtr->indicatorGC;
  1844.     if (gc == None) {
  1845.         gc = menuPtr->indicatorGC;
  1846.     }
  1847.     if ((mePtr->type == CHECK_BUTTON_ENTRY) && mePtr->indicatorOn) {
  1848.         int dim, x, y;
  1849.  
  1850.         dim = mePtr->indicatorDiameter;
  1851.         x = menuPtr->borderWidth + menuPtr->activeBorderWidth
  1852.             + (menuPtr->indicatorSpace - dim)/2;
  1853.         y = mePtr->y + (mePtr->height - dim)/2;
  1854.         Tk_Fill3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1855.             menuPtr->border, x, y, dim, dim,
  1856.             DECORATION_BORDER_WIDTH, TK_RELIEF_SUNKEN);
  1857.         x += DECORATION_BORDER_WIDTH;
  1858.         y += DECORATION_BORDER_WIDTH;
  1859.         dim -= 2*DECORATION_BORDER_WIDTH;
  1860.         if ((dim > 0) && (mePtr->flags & ENTRY_SELECTED)) {
  1861.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin), gc,
  1862.             x, y, (unsigned int) dim, (unsigned int) dim);
  1863.         }
  1864.     }
  1865.  
  1866.     /*
  1867.      * Draw radio-button indicator.
  1868.      */
  1869.  
  1870.     if ((mePtr->type == RADIO_BUTTON_ENTRY) && mePtr->indicatorOn) {
  1871.         XPoint points[4];
  1872.         int radius;
  1873.  
  1874.         radius = mePtr->indicatorDiameter/2;
  1875.         points[0].x = menuPtr->borderWidth + menuPtr->activeBorderWidth
  1876.             + (menuPtr->indicatorSpace - mePtr->indicatorDiameter)/2;
  1877.         points[0].y = mePtr->y + (mePtr->height)/2;
  1878.         points[1].x = points[0].x + radius;
  1879.         points[1].y = points[0].y + radius;
  1880.         points[2].x = points[1].x + radius;
  1881.         points[2].y = points[0].y;
  1882.         points[3].x = points[1].x;
  1883.         points[3].y = points[0].y - radius;
  1884.         if (mePtr->flags & ENTRY_SELECTED) {
  1885.         XFillPolygon(menuPtr->display, Tk_WindowId(tkwin), gc,
  1886.             points, 4, Convex, CoordModeOrigin);
  1887.         } else {
  1888.         Tk_Fill3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
  1889.             menuPtr->border, points, 4, DECORATION_BORDER_WIDTH,
  1890.             TK_RELIEF_FLAT);
  1891.         }
  1892.         Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
  1893.             menuPtr->border, points, 4, DECORATION_BORDER_WIDTH,
  1894.             TK_RELIEF_SUNKEN);
  1895.     }
  1896.  
  1897.     /*
  1898.      * Draw separator.
  1899.      */
  1900.  
  1901.     if (mePtr->type == SEPARATOR_ENTRY) {
  1902.         XPoint points[2];
  1903.         int margin;
  1904.  
  1905.         margin = (fontPtr->ascent + fontPtr->descent)/2;
  1906.         points[0].x = 0;
  1907.         points[0].y = mePtr->y + mePtr->height/2;
  1908.         points[1].x = Tk_Width(tkwin) - 1;
  1909.         points[1].y = points[0].y;
  1910.         Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
  1911.             menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
  1912.     }
  1913.  
  1914.     /*
  1915.      * Draw tear-off line.
  1916.      */
  1917.  
  1918.     if (mePtr->type == TEAROFF_ENTRY) {
  1919.         XPoint points[2];
  1920.         int margin, width, maxX;
  1921.  
  1922.         margin = (fontPtr->ascent + fontPtr->descent)/2;
  1923.         points[0].x = 0;
  1924.         points[0].y = mePtr->y + mePtr->height/2;
  1925.         points[1].y = points[0].y;
  1926.         width = 6;
  1927.         maxX  = Tk_Width(tkwin) - 1;
  1928.  
  1929.         while (points[0].x < maxX) {
  1930.         points[1].x = points[0].x + width;
  1931.         if (points[1].x > maxX) {
  1932.             points[1].x = maxX;
  1933.         }
  1934.         Tk_Draw3DPolygon(menuPtr->tkwin, Tk_WindowId(tkwin),
  1935.             menuPtr->border, points, 2, 1, TK_RELIEF_RAISED);
  1936.         points[0].x += 2*width;
  1937.         }
  1938.     }
  1939.  
  1940.     /*
  1941.      * If the entry is disabled with a stipple rather than a special
  1942.      * foreground color, generate the stippled effect.
  1943.      */
  1944.  
  1945.     if ((mePtr->state == tkDisabledUid) && (menuPtr->disabledFg == NULL)) {
  1946.         XFillRectangle(menuPtr->display, Tk_WindowId(tkwin),
  1947.             menuPtr->disabledGC, menuPtr->borderWidth,
  1948.             mePtr->y,
  1949.             (unsigned) (Tk_Width(tkwin) - 2*menuPtr->borderWidth),
  1950.             (unsigned) mePtr->height);
  1951.     }
  1952.     }
  1953.  
  1954.     Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin),
  1955.         menuPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  1956.         menuPtr->borderWidth, menuPtr->relief);
  1957. }
  1958.  
  1959. /*
  1960.  *--------------------------------------------------------------
  1961.  *
  1962.  * GetMenuIndex --
  1963.  *
  1964.  *    Parse a textual index into a menu and return the numerical
  1965.  *    index of the indicated entry.
  1966.  *
  1967.  * Results:
  1968.  *    A standard Tcl result.  If all went well, then *indexPtr is
  1969.  *    filled in with the entry index corresponding to string
  1970.  *    (ranges from -1 to the number of entries in the menu minus
  1971.  *    one).  Otherwise an error message is left in interp->result.
  1972.  *
  1973.  * Side effects:
  1974.  *    None.
  1975.  *
  1976.  *--------------------------------------------------------------
  1977.  */
  1978.  
  1979. static int
  1980. GetMenuIndex(interp, menuPtr, string, lastOK, indexPtr)
  1981.     Tcl_Interp *interp;        /* For error messages. */
  1982.     Menu *menuPtr;        /* Menu for which the index is being
  1983.                  * specified. */
  1984.     char *string;        /* Specification of an entry in menu.  See
  1985.                  * manual entry for valid .*/
  1986.     int lastOK;            /* Non-zero means its OK to return index
  1987.                  * just *after* last entry. */
  1988.     int *indexPtr;        /* Where to store converted relief. */
  1989. {
  1990.     int i, y;
  1991.  
  1992.     if ((string[0] == 'a') && (strcmp(string, "active") == 0)) {
  1993.     *indexPtr = menuPtr->active;
  1994.     return TCL_OK;
  1995.     }
  1996.  
  1997.     if (((string[0] == 'l') && (strcmp(string, "last") == 0))
  1998.         || ((string[0] == 'e') && (strcmp(string, "end") == 0))) {
  1999.     *indexPtr = menuPtr->numEntries - ((lastOK) ? 0 : 1);
  2000.     return TCL_OK;
  2001.     }
  2002.  
  2003.     if ((string[0] == 'n') && (strcmp(string, "none") == 0)) {
  2004.     *indexPtr = -1;
  2005.     return TCL_OK;
  2006.     }
  2007.  
  2008.     if (string[0] == '@') {
  2009.     if (Tcl_GetInt(interp, string+1,  &y) == TCL_OK) {
  2010.         for (i = 0; i < menuPtr->numEntries; i++) {
  2011.         MenuEntry *mePtr = menuPtr->entries[i];
  2012.  
  2013.         if (y < (mePtr->y + mePtr->height)) {
  2014.             break;
  2015.         }
  2016.         }
  2017.         if (i >= menuPtr->numEntries) {
  2018.         i = menuPtr->numEntries-1;
  2019.         }
  2020.         *indexPtr = i;
  2021.         return TCL_OK;
  2022.     } else {
  2023.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  2024.     }
  2025.     }
  2026.  
  2027.     if (isdigit(UCHAR(string[0]))) {
  2028.     if (Tcl_GetInt(interp, string,  &i) == TCL_OK) {
  2029.         if (i >= menuPtr->numEntries) {
  2030.         if (lastOK) {
  2031.             i = menuPtr->numEntries;
  2032.         } else {
  2033.             i = menuPtr->numEntries-1;
  2034.         }
  2035.         } else if (i < 0) {
  2036.         i = -1;
  2037.         }
  2038.         *indexPtr = i;
  2039.         return TCL_OK;
  2040.     }
  2041.     Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  2042.     }
  2043.  
  2044.     for (i = 0; i < menuPtr->numEntries; i++) {
  2045.     char *label;
  2046.  
  2047.     label = menuPtr->entries[i]->label;
  2048.     if ((label != NULL)
  2049.         && (Tcl_StringMatch(menuPtr->entries[i]->label, string))) {
  2050.         *indexPtr = i;
  2051.         return TCL_OK;
  2052.     }
  2053.     }
  2054.  
  2055.     Tcl_AppendResult(interp, "bad menu entry index \"",
  2056.         string, "\"", (char *) NULL);
  2057.     return TCL_ERROR;
  2058. }
  2059.  
  2060. /*
  2061.  *--------------------------------------------------------------
  2062.  *
  2063.  * MenuEventProc --
  2064.  *
  2065.  *    This procedure is invoked by the Tk dispatcher for various
  2066.  *    events on menus.
  2067.  *
  2068.  * Results:
  2069.  *    None.
  2070.  *
  2071.  * Side effects:
  2072.  *    When the window gets deleted, internal structures get
  2073.  *    cleaned up.  When it gets exposed, it is redisplayed.
  2074.  *
  2075.  *--------------------------------------------------------------
  2076.  */
  2077.  
  2078. static void
  2079. MenuEventProc(clientData, eventPtr)
  2080.     ClientData clientData;    /* Information about window. */
  2081.     XEvent *eventPtr;        /* Information about event. */
  2082. {
  2083.     Menu *menuPtr = (Menu *) clientData;
  2084.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  2085.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2086.     } else if (eventPtr->type == ConfigureNotify) {
  2087.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2088.     } else if (eventPtr->type == DestroyNotify) {
  2089.     if (menuPtr->tkwin != NULL) {
  2090.         menuPtr->tkwin = NULL;
  2091.         Tcl_DeleteCommand(menuPtr->interp,
  2092.             Tcl_GetCommandName(menuPtr->interp, menuPtr->widgetCmd));
  2093.     }
  2094.     if (menuPtr->flags & REDRAW_PENDING) {
  2095.         Tk_CancelIdleCall(DisplayMenu, (ClientData) menuPtr);
  2096.     }
  2097.     if (menuPtr->flags & RESIZE_PENDING) {
  2098.         Tk_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr);
  2099.     }
  2100.     Tk_EventuallyFree((ClientData) menuPtr, DestroyMenu);
  2101.     }
  2102. }
  2103.  
  2104. /*
  2105.  *----------------------------------------------------------------------
  2106.  *
  2107.  * MenuCmdDeletedProc --
  2108.  *
  2109.  *    This procedure is invoked when a widget command is deleted.  If
  2110.  *    the widget isn't already in the process of being destroyed,
  2111.  *    this command destroys it.
  2112.  *
  2113.  * Results:
  2114.  *    None.
  2115.  *
  2116.  * Side effects:
  2117.  *    The widget is destroyed.
  2118.  *
  2119.  *----------------------------------------------------------------------
  2120.  */
  2121.  
  2122. static void
  2123. MenuCmdDeletedProc(clientData)
  2124.     ClientData clientData;    /* Pointer to widget record for widget. */
  2125. {
  2126.     Menu *menuPtr = (Menu *) clientData;
  2127.     Tk_Window tkwin = menuPtr->tkwin;
  2128.  
  2129.     /*
  2130.      * This procedure could be invoked either because the window was
  2131.      * destroyed and the command was then deleted (in which case tkwin
  2132.      * is NULL) or because the command was deleted, and then this procedure
  2133.      * destroys the widget.
  2134.      */
  2135.  
  2136.     if (tkwin != NULL) {
  2137.     menuPtr->tkwin = NULL;
  2138.     Tk_DestroyWindow(tkwin);
  2139.     }
  2140. }
  2141.  
  2142. /*
  2143.  *----------------------------------------------------------------------
  2144.  *
  2145.  * MenuNewEntry --
  2146.  *
  2147.  *    This procedure allocates and initializes a new menu entry.
  2148.  *
  2149.  * Results:
  2150.  *    The return value is a pointer to a new menu entry structure,
  2151.  *    which has been malloc-ed, initialized, and entered into the
  2152.  *    entry array for the  menu.
  2153.  *
  2154.  * Side effects:
  2155.  *    Storage gets allocated.
  2156.  *
  2157.  *----------------------------------------------------------------------
  2158.  */
  2159.  
  2160. static MenuEntry *
  2161. MenuNewEntry(menuPtr, index, type)
  2162.     Menu *menuPtr;        /* Menu that will hold the new entry. */
  2163.     int index;            /* Where in the menu the new entry is to
  2164.                  * go. */
  2165.     int type;            /* The type of the new entry. */
  2166. {
  2167.     MenuEntry *mePtr;
  2168.     MenuEntry **newEntries;
  2169.     int i;
  2170.  
  2171.     /*
  2172.      * Create a new array of entries with an empty slot for the
  2173.      * new entry.
  2174.      */
  2175.  
  2176.     newEntries = (MenuEntry **) ckalloc((unsigned)
  2177.         ((menuPtr->numEntries+1)*sizeof(MenuEntry *)));
  2178.     for (i = 0; i < index; i++) {
  2179.     newEntries[i] = menuPtr->entries[i];
  2180.     }
  2181.     for (  ; i < menuPtr->numEntries; i++) {
  2182.     newEntries[i+1] = menuPtr->entries[i];
  2183.     }
  2184.     if (menuPtr->numEntries != 0) {
  2185.     ckfree((char *) menuPtr->entries);
  2186.     }
  2187.     menuPtr->entries = newEntries;
  2188.     menuPtr->numEntries++;
  2189.     menuPtr->entries[index] = mePtr = (MenuEntry *) ckalloc(sizeof(MenuEntry));
  2190.     mePtr->type = type;
  2191.     mePtr->menuPtr = menuPtr;
  2192.     mePtr->label = NULL;
  2193.     mePtr->labelLength = 0;
  2194.     mePtr->underline = -1;
  2195.     mePtr->bitmap = None;
  2196.     mePtr->imageString = NULL;
  2197.     mePtr->image = NULL;
  2198.     mePtr->selectImageString  = NULL;
  2199.     mePtr->selectImage = NULL;
  2200.     mePtr->accel = NULL;
  2201.     mePtr->accelLength = 0;
  2202.     mePtr->state = tkNormalUid;
  2203.     mePtr->height = 0;
  2204.     mePtr->y = 0;
  2205.     mePtr->indicatorDiameter = 0;
  2206.     mePtr->border = NULL;
  2207.     mePtr->fg = NULL;
  2208.     mePtr->activeBorder = NULL;
  2209.     mePtr->activeFg = NULL;
  2210.     mePtr->fontPtr = NULL;
  2211.     mePtr->textGC = None;
  2212.     mePtr->activeGC = None;
  2213.     mePtr->disabledGC = None;
  2214.     mePtr->indicatorOn = 1;
  2215.     mePtr->indicatorFg = NULL;
  2216.     mePtr->indicatorGC = None;
  2217.     mePtr->command = NULL;
  2218.     mePtr->name = NULL;
  2219.     mePtr->onValue = NULL;
  2220.     mePtr->offValue = NULL;
  2221.     mePtr->flags = 0;
  2222.     return mePtr;
  2223. }
  2224.  
  2225. /*
  2226.  *----------------------------------------------------------------------
  2227.  *
  2228.  * MenuAddOrInsert --
  2229.  *
  2230.  *    This procedure does all of the work of the "add" and "insert"
  2231.  *    widget commands, allowing the code for these to be shared.
  2232.  *
  2233.  * Results:
  2234.  *    A standard Tcl return value.
  2235.  *
  2236.  * Side effects:
  2237.  *    A new menu entry is created in menuPtr.
  2238.  *
  2239.  *----------------------------------------------------------------------
  2240.  */
  2241.  
  2242. static int
  2243. MenuAddOrInsert(interp, menuPtr, indexString, argc, argv)
  2244.     Tcl_Interp *interp;            /* Used for error reporting. */
  2245.     Menu *menuPtr;            /* Widget in which to create new
  2246.                      * entry. */
  2247.     char *indexString;            /* String describing index at which
  2248.                      * to insert.  NULL means insert at
  2249.                      * end. */
  2250.     int argc;                /* Number of elements in argv. */
  2251.     char **argv;            /* Arguments to command:  first arg
  2252.                      * is type of entry, others are
  2253.                      * config options. */
  2254. {
  2255.     int c, type, i, index;
  2256.     size_t length;
  2257.     MenuEntry *mePtr;
  2258.  
  2259.     if (indexString != NULL) {
  2260.     if (GetMenuIndex(interp, menuPtr, indexString, 1, &index) != TCL_OK) {
  2261.         return TCL_ERROR;
  2262.     }
  2263.     } else {
  2264.     index = menuPtr->numEntries;
  2265.     }
  2266.     if (index < 0) {
  2267.     Tcl_AppendResult(interp, "bad index \"", indexString, "\"",
  2268.          (char *) NULL);
  2269.     return TCL_ERROR;
  2270.     }
  2271.     if (menuPtr->tearOff && (index == 0)) {
  2272.     index = 1;
  2273.     }
  2274.  
  2275.     /*
  2276.      * Figure out the type of the new entry.
  2277.      */
  2278.  
  2279.     c = argv[0][0];
  2280.     length = strlen(argv[0]);
  2281.     if ((c == 'c') && (strncmp(argv[0], "cascade", length) == 0)
  2282.         && (length >= 2)) {
  2283.     type = CASCADE_ENTRY;
  2284.     } else if ((c == 'c') && (strncmp(argv[0], "checkbutton", length) == 0)
  2285.         && (length >= 2)) {
  2286.     type = CHECK_BUTTON_ENTRY;
  2287.     } else if ((c == 'c') && (strncmp(argv[0], "command", length) == 0)
  2288.         && (length >= 2)) {
  2289.     type = COMMAND_ENTRY;
  2290.     } else if ((c == 'r')
  2291.         && (strncmp(argv[0], "radiobutton", length) == 0)) {
  2292.     type = RADIO_BUTTON_ENTRY;
  2293.     } else if ((c == 's')
  2294.         && (strncmp(argv[0], "separator", length) == 0)) {
  2295.     type = SEPARATOR_ENTRY;
  2296.     } else {
  2297.     Tcl_AppendResult(interp, "bad menu entry type \"",
  2298.         argv[0], "\":  must be cascade, checkbutton, ",
  2299.         "command, radiobutton, or separator", (char *) NULL);
  2300.     return TCL_ERROR;
  2301.     }
  2302.     mePtr = MenuNewEntry(menuPtr, index, type);
  2303.     if (ConfigureMenuEntry(interp, menuPtr, mePtr, index,
  2304.         argc-1, argv+1, 0) != TCL_OK) {
  2305.     DestroyMenuEntry((ClientData) mePtr);
  2306.     for (i = index+1; i < menuPtr->numEntries; i++) {
  2307.         menuPtr->entries[i-1] = menuPtr->entries[i];
  2308.     }
  2309.     menuPtr->numEntries--;
  2310.     return TCL_ERROR;
  2311.     }
  2312.     return TCL_OK;
  2313. }
  2314.  
  2315. /*
  2316.  *--------------------------------------------------------------
  2317.  *
  2318.  * MenuVarProc --
  2319.  *
  2320.  *    This procedure is invoked when someone changes the
  2321.  *    state variable associated with a radiobutton or checkbutton
  2322.  *    menu entry.  The entry's selected state is set to match
  2323.  *    the value of the variable.
  2324.  *
  2325.  * Results:
  2326.  *    NULL is always returned.
  2327.  *
  2328.  * Side effects:
  2329.  *    The menu entry may become selected or deselected.
  2330.  *
  2331.  *--------------------------------------------------------------
  2332.  */
  2333.  
  2334.     /* ARGSUSED */
  2335. static char *
  2336. MenuVarProc(clientData, interp, name1, name2, flags)
  2337.     ClientData clientData;    /* Information about menu entry. */
  2338.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  2339.     char *name1;        /* First part of variable's name. */
  2340.     char *name2;        /* Second part of variable's name. */
  2341.     int flags;            /* Describes what just happened. */
  2342. {
  2343.     MenuEntry *mePtr = (MenuEntry *) clientData;
  2344.     Menu *menuPtr;
  2345.     char *value;
  2346.  
  2347.     menuPtr = mePtr->menuPtr;
  2348.  
  2349.     /*
  2350.      * If the variable is being unset, then re-establish the
  2351.      * trace unless the whole interpreter is going away.
  2352.      */
  2353.  
  2354.     if (flags & TCL_TRACE_UNSETS) {
  2355.     mePtr->flags &= ~ENTRY_SELECTED;
  2356.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  2357.         Tcl_TraceVar(interp, mePtr->name,
  2358.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  2359.             MenuVarProc, clientData);
  2360.     }
  2361.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2362.     return (char *) NULL;
  2363.     }
  2364.  
  2365.     /*
  2366.      * Use the value of the variable to update the selected status of
  2367.      * the menu entry.
  2368.      */
  2369.  
  2370.     value = Tcl_GetVar(interp, mePtr->name, TCL_GLOBAL_ONLY);
  2371.     if (strcmp(value, mePtr->onValue) == 0) {
  2372.     if (mePtr->flags & ENTRY_SELECTED) {
  2373.         return (char *) NULL;
  2374.     }
  2375.     mePtr->flags |= ENTRY_SELECTED;
  2376.     } else if (mePtr->flags & ENTRY_SELECTED) {
  2377.     mePtr->flags &= ~ENTRY_SELECTED;
  2378.     } else {
  2379.     return (char *) NULL;
  2380.     }
  2381.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2382.     return (char *) NULL;
  2383. }
  2384.  
  2385. /*
  2386.  *----------------------------------------------------------------------
  2387.  *
  2388.  * EventuallyRedrawMenu --
  2389.  *
  2390.  *    Arrange for an entry of a menu, or the whole menu, to be
  2391.  *    redisplayed at some point in the future.
  2392.  *
  2393.  * Results:
  2394.  *    None.
  2395.  *
  2396.  * Side effects:
  2397.  *    A when-idle hander is scheduled to do the redisplay, if there
  2398.  *    isn't one already scheduled.
  2399.  *
  2400.  *----------------------------------------------------------------------
  2401.  */
  2402.  
  2403. static void
  2404. EventuallyRedrawMenu(menuPtr, mePtr)
  2405.     register Menu *menuPtr;    /* Information about menu to redraw. */
  2406.     register MenuEntry *mePtr;    /* Entry to redraw.  NULL means redraw
  2407.                  * all the entries in the menu. */
  2408. {
  2409.     int i;
  2410.     if (menuPtr->tkwin == NULL) {
  2411.     return;
  2412.     }
  2413.     if (mePtr != NULL) {
  2414.     mePtr->flags |= ENTRY_NEEDS_REDISPLAY;
  2415.     } else {
  2416.     for (i = 0; i < menuPtr->numEntries; i++) {
  2417.         menuPtr->entries[i]->flags |= ENTRY_NEEDS_REDISPLAY;
  2418.     }
  2419.     }
  2420.     if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(menuPtr->tkwin)
  2421.         || (menuPtr->flags & REDRAW_PENDING)) {
  2422.     return;
  2423.     }
  2424.     Tk_DoWhenIdle(DisplayMenu, (ClientData) menuPtr);
  2425.     menuPtr->flags |= REDRAW_PENDING;
  2426. }
  2427.  
  2428. /*
  2429.  *--------------------------------------------------------------
  2430.  *
  2431.  * PostSubmenu --
  2432.  *
  2433.  *    This procedure arranges for a particular submenu (i.e. the
  2434.  *    menu corresponding to a given cascade entry) to be
  2435.  *    posted.
  2436.  *
  2437.  * Results:
  2438.  *    A standard Tcl return result.  Errors may occur in the
  2439.  *    Tcl commands generated to post and unpost submenus.
  2440.  *
  2441.  * Side effects:
  2442.  *    If there is already a submenu posted, it is unposted.
  2443.  *    The new submenu is then posted.
  2444.  *
  2445.  *--------------------------------------------------------------
  2446.  */
  2447.  
  2448. static int
  2449. PostSubmenu(interp, menuPtr, mePtr)
  2450.     Tcl_Interp *interp;        /* Used for invoking sub-commands and
  2451.                  * reporting errors. */
  2452.     register Menu *menuPtr;    /* Information about menu as a whole. */
  2453.     register MenuEntry *mePtr;    /* Info about submenu that is to be
  2454.                  * posted.  NULL means make sure that
  2455.                  * no submenu is posted. */
  2456. {
  2457.     char string[30];
  2458.     int result, x, y;
  2459.     Tk_Window tkwin;
  2460.  
  2461.     if (mePtr == menuPtr->postedCascade) {
  2462.     return TCL_OK;
  2463.     }
  2464.  
  2465.     if (menuPtr->postedCascade != NULL) {
  2466.     /*
  2467.      * Note: when unposting a submenu, we have to redraw the entire
  2468.      * parent menu.  This is because of a combination of the following
  2469.      * things:
  2470.      * (a) the submenu partially overlaps the parent.
  2471.      * (b) the submenu specifies "save under", which causes the X
  2472.      *     server to make a copy of the information under it when it
  2473.      *     is posted.  When the submenu is unposted, the X server
  2474.      *     copies this data back and doesn't generate any Expose
  2475.      *     events for the parent.
  2476.      * (c) the parent may have redisplayed itself after the submenu
  2477.      *     was posted, in which case the saved information is no
  2478.      *     longer correct.
  2479.      * The simplest solution is just force a complete redisplay of
  2480.      * the parent.
  2481.      */
  2482.  
  2483.     EventuallyRedrawMenu(menuPtr, (MenuEntry *) NULL);
  2484.     result = Tcl_VarEval(interp, menuPtr->postedCascade->name,
  2485.         " unpost", (char *) NULL);
  2486.     menuPtr->postedCascade = NULL;
  2487.     if (result != TCL_OK) {
  2488.         return result;
  2489.     }
  2490.     }
  2491.  
  2492.     if ((mePtr != NULL) && (mePtr->name != NULL)
  2493.         && Tk_IsMapped(menuPtr->tkwin)) {
  2494.     /*
  2495.      * Make sure that the cascaded submenu is a child of the
  2496.      * parent menu.
  2497.      */
  2498.  
  2499.     tkwin = Tk_NameToWindow(interp, mePtr->name, menuPtr->tkwin);
  2500.     if (tkwin == NULL) {
  2501.         return TCL_ERROR;
  2502.     }
  2503.     if (Tk_Parent(tkwin) != menuPtr->tkwin) {
  2504.         Tcl_AppendResult(interp, "cascaded sub-menu ",
  2505.             Tk_PathName(tkwin), " must be a child of ",
  2506.             Tk_PathName(menuPtr->tkwin), (char *) NULL);
  2507.         return TCL_ERROR;
  2508.     }
  2509.  
  2510.     /*
  2511.      * Position the cascade with its upper left corner slightly
  2512.      * below and to the left of the upper right corner of the
  2513.      * menu entry (this is an attempt to match Motif behavior).
  2514.      */
  2515.     Tk_GetRootCoords(menuPtr->tkwin, &x, &y);
  2516.     x += Tk_Width(menuPtr->tkwin) - menuPtr->borderWidth
  2517.         - menuPtr->activeBorderWidth - 2;
  2518.     y += mePtr->y + menuPtr->activeBorderWidth + 2;
  2519.     sprintf(string, "%d %d", x, y);
  2520.     result = Tcl_VarEval(interp, mePtr->name, " post ", string,
  2521.         (char *) NULL);
  2522.     if (result != TCL_OK) {
  2523.         return result;
  2524.     }
  2525.     menuPtr->postedCascade = mePtr;
  2526.     }
  2527.     return TCL_OK;
  2528. }
  2529.  
  2530. /*
  2531.  *----------------------------------------------------------------------
  2532.  *
  2533.  * ActivateMenuEntry --
  2534.  *
  2535.  *    This procedure is invoked to make a particular menu entry
  2536.  *    the active one, deactivating any other entry that might
  2537.  *    currently be active.
  2538.  *
  2539.  * Results:
  2540.  *    The return value is a standard Tcl result (errors can occur
  2541.  *    while posting and unposting submenus).
  2542.  *
  2543.  * Side effects:
  2544.  *    Menu entries get redisplayed, and the active entry changes.
  2545.  *    Submenus may get posted and unposted.
  2546.  *
  2547.  *----------------------------------------------------------------------
  2548.  */
  2549.  
  2550. static int
  2551. ActivateMenuEntry(menuPtr, index)
  2552.     register Menu *menuPtr;        /* Menu in which to activate. */
  2553.     int index;                /* Index of entry to activate, or
  2554.                      * -1 to deactivate all entries. */
  2555. {
  2556.     register MenuEntry *mePtr;
  2557.     int result = TCL_OK;
  2558.  
  2559.     if (menuPtr->active >= 0) {
  2560.     mePtr = menuPtr->entries[menuPtr->active];
  2561.  
  2562.     /*
  2563.      * Don't change the state unless it's currently active (state
  2564.      * might already have been changed to disabled).
  2565.      */
  2566.  
  2567.     if (mePtr->state == tkActiveUid) {
  2568.         mePtr->state = tkNormalUid;
  2569.     }
  2570.     EventuallyRedrawMenu(menuPtr, menuPtr->entries[menuPtr->active]);
  2571.     }
  2572.     menuPtr->active = index;
  2573.     if (index >= 0) {
  2574.     mePtr = menuPtr->entries[index];
  2575.     mePtr->state = tkActiveUid;
  2576.     EventuallyRedrawMenu(menuPtr, mePtr);
  2577.     }
  2578.     return result;
  2579. }
  2580.  
  2581. /*
  2582.  *----------------------------------------------------------------------
  2583.  *
  2584.  * MenuImageProc --
  2585.  *
  2586.  *    This procedure is invoked by the image code whenever the manager
  2587.  *    for an image does something that affects the size of contents
  2588.  *    of an image displayed in a menu entry.
  2589.  *
  2590.  * Results:
  2591.  *    None.
  2592.  *
  2593.  * Side effects:
  2594.  *    Arranges for the menu to get redisplayed.
  2595.  *
  2596.  *----------------------------------------------------------------------
  2597.  */
  2598.  
  2599. static void
  2600. MenuImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
  2601.     ClientData clientData;        /* Pointer to widget record. */
  2602.     int x, y;                /* Upper left pixel (within image)
  2603.                          * that must be redisplayed. */
  2604.     int width, height;        /* Dimensions of area to redisplay
  2605.                      * (may be <= 0). */
  2606.     int imgWidth, imgHeight;        /* New dimensions of image. */
  2607. {
  2608.     register Menu *menuPtr = ((MenuEntry *) clientData)->menuPtr;
  2609.  
  2610.     if ((menuPtr->tkwin != NULL) && !(menuPtr->flags & RESIZE_PENDING)) {
  2611.     menuPtr->flags |= RESIZE_PENDING;
  2612.     Tk_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr);
  2613.     }
  2614. }
  2615.  
  2616. /*
  2617.  *----------------------------------------------------------------------
  2618.  *
  2619.  * MenuSelectImageProc --
  2620.  *
  2621.  *    This procedure is invoked by the image code whenever the manager
  2622.  *    for an image does something that affects the size of contents
  2623.  *    of an image displayed in a menu entry when it is selected.
  2624.  *
  2625.  * Results:
  2626.  *    None.
  2627.  *
  2628.  * Side effects:
  2629.  *    Arranges for the menu to get redisplayed.
  2630.  *
  2631.  *----------------------------------------------------------------------
  2632.  */
  2633.  
  2634. static void
  2635. MenuSelectImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
  2636.     ClientData clientData;        /* Pointer to widget record. */
  2637.     int x, y;                /* Upper left pixel (within image)
  2638.                      * that must be redisplayed. */
  2639.     int width, height;            /* Dimensions of area to redisplay
  2640.                      * (may be <= 0). */
  2641.     int imgWidth, imgHeight;        /* New dimensions of image. */
  2642. {
  2643.     register MenuEntry *mePtr = (MenuEntry *) clientData;
  2644.  
  2645.     if ((mePtr->flags & ENTRY_SELECTED)
  2646.         && !(mePtr->menuPtr->flags & REDRAW_PENDING)) {
  2647.     mePtr->menuPtr->flags |= REDRAW_PENDING;
  2648.     Tk_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr);
  2649.     }
  2650. }
  2651.